Do it! 알고리즘 입문: 자바 편 (13)[책리뷰 & Book review]
본문 바로가기

컴퓨터공부/책리뷰 & book review

Do it! 알고리즘 입문: 자바 편 (13)[책리뷰 & Book review]

by Life & study 2024. 1. 31.
반응형

 

 

 

 Do it! 알고리즘 입문: 자바 편 (13)[책리뷰 & Book review]

Java에서 배열과 스트림(Stream)의 차이점은 다음과 같습니다:

배열은 고정된 크기를 가지는 데이터 구조입니다. 반면, 스트림은 데이터의 흐름을 나타내는 데이터 구조입니다.
배열은 인덱스를 사용하여 데이터에 접근합니다.  반면, 스트림은 순차적으로 데이터에 접근합니다.



배열은 메모리에 저장됩니다. 

먼저 배열의 작동 방식에 대해서 설명하겠습니다.

배열은 메모리에 연속적으로 저장되며, 각 원소는 인덱스를 통해 접근할 수 있습니다.

java

int[] arr = {1, 2, 3, 4, 5}; // 배열 선언 및 초기화
for (int i = 0; i < arr.length; i++) { // 배열의 모든 원소에 접근
    System.out.println(arr[i]); // 각 원소 출력
}


위의 예시에서, 배열 arr은 메모리에 [1, 2, 3, 4, 5]와 같이 연속적으로 저장되며, 

각 원소는 인덱스 (0, 1, 2, 3, 4)를 통해 접근할 수 있습니다.



Array 이제 스트림

이제 스트림의 작동 방식에 대해 설명하겠습니다.

스트림은 데이터의 흐름을 나타내는 추상적인 개념입니다. 

스트림은 데이터 소스에서 데이터를 읽고, 데이터에 대한 연산을 수행하며, 결과를 생성합니다.

java

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5); // 스트림 생성
stream.map(n -> n * n) // 각 원소에 대해 제곱 연산 수행
      .forEach(System.out::println); // 각 원소 출력


위의 예시에서, 스트림은 원본 데이터

 [1, 2, 3, 4, 5]에서 데이터를 읽어 들이고, 각 원소에 대해 제곱 연산을 수행한 후, 결과를 출력합니다.

스트림의 작동 방식을 도식화하면 다음과 같습니다:

[Data source] -> |Stream| -> [Operations] -> [Result]


스트림은 데이터를 메모리에 저장하지 않으며, 데이터 소스에서 데이터를 읽고, 연산을 수행하고, 결과를 생성하는 '데이터의 흐름'을 나타냅니다. 따라서, 스트림은 대용량 데이터 처리에 적합하며, 병렬 처리를 지원하여 성능을 향상할 수 있습니다.


반면, 스트림은 메모리에 저장되지 않습니다. 대신, 스트림은 원본 데이터에서 데이터를 가져옵니다.


배열은 변경 가능합니다. 

반면, 스트림은 변경 불가능합니다.


배열은 다양한 데이터 유형을 포함할 수 있습니다. 반면, 스트림은 같은 데이터 유형만 포함할 수 있습니다.
배열은 반복문을 사용하여 데이터를 처리합니다. 반면, 스트림은 함수형 프로그래밍을 사용하여 데이터를 처리합니다.

 



Java에서 배열과 스트림은 서로 다른 목적을 가진 데이터 구조입니다. 

 

배열은 고정된 크기의 데이터를 저장하고 처리하는 데 사용되며,

스트림은 데이터의 흐름을 나타내는 데이터 구조로 사용됩니다.

 

이러한 차이점을 고려하여, Java에서 배열과 스트림을 사용하는 방법을 결정할 수 있습니다.


Array

예를 들어, 아래는 자바에서 배열을 사용하는 예제코드입니다.

java

// 배열 선언 및 초기화
int[] numbers = new int[5];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;

// 배열의 모든 원소에 접근
for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);  // 인덱스 i의 원소 출력
}


다음으로 스트림에 대해 설명하겠습니다.

스트림은 원하는 시점에 데이터를 가져오는 데이터 흐름을 나타내는 데이터 구조입니다. 데이터의 크기와 상관 없이 큰 데이터를 효율적으로 처리할 수 있습니다.

스트림의 작동 방식은 아래와 같습니다.

[Data Source] --> | Stream | --> [Operations] --> [Results]


예를 들어, 아래는 자바에서 스트림을 사용하는 예제코드입니다.

java

// 스트림 생성
Stream<Integer> numbersStream = Stream.of(1, 2, 3, 4, 5);

// 스트림의 모든 원소에 접근 및 연산 수행
numbersStream.map(number -> number * number)  // 각 원소를 제곱
             .forEach(System.out::println);  // 각 원소 출력


이러한 방식으로, 배열은 고정된 크기의 데이터를 저장하고 처리하는 데 사용되며, 

스트림은 데이터의 흐름을 나타내며, 필요에 따라 연산을 수행하고 결과를 생성합니다

 

 

 

필더 패턴은 무엇인가?

빌더 패턴(Builder Pattern)은 복잡한 객체를 생성하는 방법을 정의하는 디자인 패턴입니다. 

이 패턴은 객체의 생성과 표현을 분리하여 동일한 생성 과정에서 다양한 표현 결과를 만들 수 있게 합니다1.

빌더 패턴은 주로 다음과 같은 상황에서 사용됩니다:

객체 생성 과정이 복잡한 경우
객체 생성에 필요한 매개변수가 많은 경우
객체의 불변성을 유지하면서 다양한 표현을 생성하고자 하는 경우
Java에서 빌더 패턴을 사용하는 방법을 설명해드리겠습니다. 아래는 Computer라는 클래스를 빌더 패턴으로 구현한 예제입니다:

Java

public class Computer {
    // 필수 매개변수
    private String HDD;
    private String RAM;

    // 선택적 매개변수
    private boolean isGraphicsCardEnabled;
    private boolean isBluetoothEnabled;

    private Computer(ComputerBuilder builder) {
        this.HDD = builder.HDD;
        this.RAM = builder.RAM;
        this.isGraphicsCardEnabled = builder.isGraphicsCardEnabled;
        this.isBluetoothEnabled = builder.isBluetoothEnabled;
    }



 

 // 빌더 클래스
    public static class ComputerBuilder {
        // 필수 매개변수
        private String HDD;
        private String RAM;

        // 선택적 매개변수
        private boolean isGraphicsCardEnabled;
        private boolean isBluetoothEnabled;

        public ComputerBuilder(String HDD, String RAM) {
            this.HDD = HDD;
            this.RAM = RAM;
        }

        public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
            this.isGraphicsCardEnabled = isGraphicsCardEnabled;
            return this;
        }

        public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
            this.isBluetoothEnabled = isBluetoothEnabled;
            return this;
        }

        public Computer build() {
            return new Computer(this);
        }
    }
}



위 코드에서 ComputerBuilder는 Computer 객체를 생성하는 데 필요한 모든 매개변수를 가지고 있습니다. setGraphicsCardEnabled와 setBluetoothEnabled 메서드는 선택적 매개변수를 설정하며, 

이 메소드들은 자신을 반환함으로써 메서드 체이닝을 가능하게 합니다. 

마지막으로, build 메소드는 최종적으로 Computer 객체를 생성하고 반환합니다


 

필더 패턴은 객체 지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로, 

주로 복잡한 객체의 생성 과정을 단순화시키기 위해 사용됩니다. 

 

이 패턴은 생성자에 많은 인자가 필요한 경우, 특히 많은 수의 선택적 인자가 있는 경우에 유용합니다.

필더 패턴의 작동 방식은 아래와 같습니다:

빌더 클래스는 생성하려는 클래스의 모든 필드를 가지고 있습니다.
빌더 클래스는 각 필드를 설정하는 메서드를 제공합니다.

 

 이 메서드들은 메서드 체이닝을 지원하기 위해 빌더 객체 자체를 반환합니다.


빌더 클래스는 마지막에 build() 또는 create() 같은 메서드를 제공하여, 모든 필드가 설정된 후 최종 객체를 생성합니다.
자바에서 필더 패턴을 사용한 코드 예제는 다음과 같습니다:

java

public class User {
    private final String firstName; // 필수
    private final String lastName; // 필수
    private final int age; // 선택적
    private final String phone; // 선택적
    private final String address; // 선택적

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;
        private String address;

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}


위의 예제에서는 User 객체를 생성하는 데 필요한 필수 필드인 firstName과 lastName이 있고, 

선택적 필드인 age, phone, address가 있습니다. 

 

UserBuilder 클래스를 통해 User 객체를 생성할 때 필요한 필드만 설정하고, 

build() 메서드를 호출하여 User 객체를 완성할 수 있습니다.

이런 방식으로 사용하면 다음과 같이 간결하게 User 객체를 생성할 수 있습니다:

java

User user = new User.UserBuilder("길동", "홍")
                    .age(25)
                    .phone("010-1234-5678")
                    .address("서울")
                    .build();


이렇게 필더 패턴을 사용하면, 많은 수의 필드를 가진 객체를 생성하거나

 선택적 필드가 많은 객체를 생성할 때 코드의 가독성을 높일 수 있습니다.

 

 

 

 

디자인 패턴에 대한 순위

 

1. **싱글턴 패턴(Singleton Pattern)**: 인스턴스가 오직 하나만 생성되어야 할 때 사용되는 패턴입니다.
2. **팩토리 메서드 패턴(Factory Method Pattern)**: 객체 생성 처리를 서브클래스로 분리해 캡슐화하는 패턴입니다.
3. **전략 패턴(Strategy Pattern)**: 실행 중에 알고리즘을 선택할 수 있는 패턴으로, 알고리즘 가족을 정의하고 각각을 캡슐화하여 교환 가능하게 만듭니다.
4. **옵저버 패턴(Observer Pattern)**: 객체의 상태 변화를 관찰하는 관찰자들에게 통지하는 패턴입니다.
5. **데코레이터 패턴(Decorator Pattern)**: 객체에 추가적인 책임을 동적으로 첨가하는 패턴입니다.

 

 

 

 

1. **싱글턴 패턴(Singleton Pattern)**:

 

싱글턴 패턴(Singleton Pattern)**: 인스턴스가 오직 하나만 생성되어야 할 때 사용되는 패턴입니다.

싱글턴 패턴(Singleton Pattern)은 클래스의 인스턴스가 오직 하나만 생성되어야 할 때 사용되는 패턴입니다. 

이 패턴은 전역 변수를 사용하지 않고 객체를 전역적으로 액세스 할 수 있게 해 주며, 동시에 단일 인스턴스를 보장합니다.

다음은 싱글턴 패턴의 작동 방식을 나타내는 간단한 다이어그램입니다:

Singleton
---------
+ getInstance(): Singleton
- Singleton()


Singleton 클래스에는 getInstance()라는 공개 메서드가 있습니다. 이 메서드를 통해 싱글턴 객체에 접근할 수 있습니다.
Singleton() 생성자는 private로 선언되어 외부에서 직접 인스턴스를 생성할 수 없습니다. 이렇게 하면 인스턴스가 하나만 생성되는 것이 보장됩니다.
다음은 싱글턴 패턴을 사용하는 자바 코드의 예시입니다:

java

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {
        // Private Constructor
    }

    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}


위 코드에서 Singleton 클래스의 생성자는 private로 선언되어 있어, 

외부에서 new Singleton()을 통해 인스턴스를 생성할 수 없습니다.

getInstance() 메서드는 싱글턴 인스턴스를 반환하는 메서드로, 

처음 호출될 때 uniqueInstance가 null인지 확인하고,

null일 경우에만 new Singleton()을 호출하여 싱글턴 인스턴스를 생성합니다.

이렇게 해서, 어디에서든 Singleton.getInstance()를 호출하면 항상 동일한 Singleton 인스턴스를 반환하게 됩니다.

 

 

 

 

 

 

 

싱글턴 패턴이 사용되는 예

 

싱글턴 패턴(Singleton Pattern)은 다양한 상황에서 사용됩니다. 

그 중 몇 가지 예를 들어보겠습니다:

데이터베이스 연결: 데이터베이스 연결은 애플리케이션에서 공유되는 자원으로, 매번 연결을 생성하는 것은 비효율적입니다. 이럴 때 싱글턴 패턴을 사용하여 하나의 데이터베이스 연결 인스턴스를 만들고 이를 공유하도록 할 수 있습니다.

 

데이터베이스 연결: 데이터베이스 연결은 시스템 리소스를 많이 사용하므로, 
하나의 연결을 여러 곳에서 재사용하는 것이 효율적입니다1.

로거(Logger): 로그를 기록하는 로거 객체는 여러 곳에서 공유하여 사용할 수 있습니다1.

설정 파일 로딩: 설정 정보를 담은 파일은 한 번만 읽어서 메모리에 저장하고, 
이 정보를 필요로 하는 여러 곳에서 재사용할 수 있습니다1.

캐시 시스템: 캐시는 자주 사용되는 데이터를 빠르게 접근할 수 있도록 메모리에 저장하는데, 
이 캐시 시스템을 여러 곳에서 공유하여 사용할 수 있습니다1.

 

java

public class DatabaseSingleton {
    private static DatabaseSingleton instance;
    private Connection connection;

    private DatabaseSingleton() {
        this.connection = // 데이터베이스 연결 코드
    }

    public static DatabaseSingleton getInstance() {
        if (instance == null) {
            instance = new DatabaseSingleton();
        }
        return instance;
    }

    public Connection getConnection() {
        return connection;
    }
}


로거(Logger): 로깅은 애플리케이션 전체에서 공유되는 기능으로, 

싱글턴 패턴을 적용하여 로거 인스턴스를 공유하도록 할 수 있습니다.


java

public class LoggerSingleton {
    private static LoggerSingleton instance;
    private String logData;

    private LoggerSingleton() {
        this.logData = "";
    }

    public static LoggerSingleton getInstance() {
        if (instance == null) {
            instance = new LoggerSingleton();
        }
        return instance;
    }

    public void log(String message) {
        logData += message + "\n";
    }

    public void printLog() {
        System.out.println(logData);
    }
}


설정 파일 로드: 애플리케이션 설정을 로드하는 경우에도 싱글턴 패턴을 적용할 수 있습니다. 

설정을 로드하는 비용이 크거나 설정이 애플리케이션 전체에서 공유되어야 하는 경우 유용합니다.


java

public class ConfigSingleton {
    private static ConfigSingleton instance;
    private Properties properties;

    private ConfigSingleton() {
        this.properties = new Properties();
        // 설정 로드 코드
    }

    public static ConfigSingleton getInstance() {
        if (instance == null) {
            instance = new ConfigSingleton();
        }
        return instance;
    }

    public String getProperty(String key) {
        return properties.getProperty(key);
    }
}


이러한 예들처럼, 싱글턴 패턴은 공유되어야 하는 자원이나 서비스가 있을 때 유용하게 사용될 수 있습니다. 

하지만 싱글턴 패턴을 과도하게 사용하면 코드의 결합도가 높아져 유지보수가 어려워질 수 있으므로 주의해야 합니다.

 

 

 

 

반응형

댓글