[CS] 싱글톤 패턴, 팩토리 패턴

최지나·2023년 10월 21일
2

CS

목록 보기
10/55

디자인 패턴 중 객체 생성 방법이 들어간 생성 패턴의 대표적인 예시인 싱글톤 패턴과 팩토리 패턴에 대해 알아보자 😀

싱글톤 패턴 (Singleton)

하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴

  • 하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수 있지만, 그렇게 하지 않고 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는데 사용된다
  • 인스턴스 생성에 많은 코스트가 드는 데이터베이스 연결 모듈에 많이 쓰인다

1. 싱글톤 패턴의 장점

  • 하나의 인스턴스를 기반으로 해당 인스턴스를 다른 모듈들이 공유하여 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어든다.
  • "인스턴스 생성에 많은 비용이 드는" I/O 바운드 작업에 많이 사용
    • I/O 바운드: 디스크 연결, 네트워크 통신(API 요청), 데이터베이스 연결
    • 보통 서버와 db는 서로 다른 컨테이너에서 분리되어 구축. server에서 db에 connect 후 query 보내는 과정에서 connect를 매번 하는 것은 비효율적

2. 싱글톤 패턴의 단점

  • 의존성이 높아지며 TDD를 할 때 걸림돌이 된다. 단위 테스트는 테스트가 서로 독립적이고 테스트를 어떤 순서로든 실행할 수 있어야 하지만, 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 '독립적인' 인스턴스를 만들기가 어려움. 테스트 순서에 따라 결과가 달라질 수 있다
    • 의존성이 높다 = 종속성이 높다. A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야함을 의미

3. 예시

public class DatabaseConnection {
    private static DatabaseConnection instance;
    private String url;

    private DatabaseConnection(String url) {
        this.url = url;
    }

    public static DatabaseConnection getInstance(String url) {
        if (instance == null) {
            instance = new DatabaseConnection(url);
        }
        return instance;
    }

    public String connect() {
        return this.url;
    }

    public static void main(String[] args) {
        String URL = "mongodb://localhost:27017/startapp";
        DatabaseConnection a = DatabaseConnection.getInstance(URL);
        DatabaseConnection b = DatabaseConnection.getInstance(URL);
        System.out.println(a == b); // true
    }
}
  • java 싱글톤 패턴
    • java에서 싱글톤 패턴을 구현하는 방법은 다양: enum, LazyHolder(중첩 클래스) , 정적 블록| 정적 멤버, synchronized(특정 스레드가 메서드 호출 시 다른 스레드가 접근 못하도록 잠금 -> lock이 걸려 성능 저하), 이중 확인 잠금(DCL, 싱글톤 패턴 잠금 전, 객체 생성 전 2번 확인하여 인스턴스가 존재하지 않을 때만 잠금) 등. 그중 중첩 클래스와 enum 방식이 추천된다

ex) LazyHolder 방식

  • singleInstanceHolder 라는 내부 클래스를 하나 더 만듦으로써, JVM 클래스 로딩 시에 초기화가 되지 않고 getInstance()가 호출 될 때 정적 singleInstanceHolder class가 로딩되며 인스턴스가 생성. 불필요한 자원 할당 방지
  • 클래스안에 클래스(Holder), static이며 중첩된 클래스인 singleInstanceHolder를
    기반으로 객체를 선언했기 때문에 한 번만 로드되므로 싱글톤 클래스의 인스턴스는 애플리케이션 당 하나만 존재 -> 클래스가 두 번 로드되지 않기 때문에 두 스레드가 동일한 JVM에서 2개의 인스턴스를 생성할 수 없음
  • final 키워드를 통해서 read only 즉, 다시 값이 할당되지 않음
class Singleton {
    private static class singleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return singleInstanceHolder.INSTANCE;
    }
}

public class HelloWorld{
     public static void main(String []args){
        Singleton a = Singleton.getInstance();
        Singleton b = Singleton.getInstance();
        if (a == b){
         System.out.println(true);  // 출력됨
        }
     }
}

팩토리 패턴

상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 대한 구체적인 내용을 결정하는 패턴

  • 상위 클래스에서는 객체 생성 방식에 대해 알 필요가 없어져 유연성을 갖게 되며 객체 생성 로직은 하위 클래스에서만 관리되기 때문에 유지보수성이 증가(특정 객체 생성 로직을 바꿔야 하는 상황이 올때 상위 클래스까지 수정할 필요가 없음)

ex) 바리스타 공장 (커피 팩토리)

  • 라떼 레시피 아메리카노 레시피,,,,
  • 바리스타 공장이라는 상위 클래스는 어떤 품목들을 취급할 건지만 알고 있고 각 품목들의 생성 방법은 하위 클래스에서 관리 상위 클래스에서는 실행만 시킴
public enum CoffeeType {
    LATTE,
    ESPRESSO
}

abstract class Coffee {
    protected String name;

    public String getName() {
        return name;
    }
}

class Latte extends Coffee {
    public Latte() {
        name = "latte";
    }
}

class Espresso extends Coffee {
    public Espresso() {
        name = "Espresso";
    }
}

class CoffeeFactory {
    public static Coffee createCoffee(CoffeeType type) {
        switch (type) {
            case LATTE:
                return new Latte();
            case ESPRESSO:
                return new Espresso();
            default:
                throw new IllegalArgumentException("Invalid coffee type: " + type);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
        System.out.println(coffee.getName());
    }
}

REF

profile
의견 나누는 것을 좋아합니다 ლ(・ヮ・ლ)

0개의 댓글