[Effective Java] 아이템 3 : private 생성자나 열거 타입으로 싱글턴임을 보증하라

2022. 3. 10. 14:14Java/Effective Java

728x90
반응형

Singleton


  • 싱글톤 패턴은 인스턴스화를 제한하고, 클래스의 하나의 인스턴스만 JVM에 존재하도록 한다.
  • 싱글톤 패턴은 Global access point에서 인스턴스를 가져올 수 있도록 제공한다.
  • 로깅, 드라이버 개체, 캐싱 및 스레드 풀에 사용된다.
  • Abstract Factory, Builder, Protorype and Facade 등과 같은 디자인 패턴에도 사용된다.

장점

  1. 객체를 생성하면 재사용이 가능해서 메모리 사용 낭비를 막을 수 있다.
  2. 전역 객체이기 때문에 다른 객체와도 공유가 가능하다.

단점

  1. 클래스의 싱글턴화 시 클라이언트 테스트가 어려워진다.
  2. 타입을 인스턴스 정의 시 구현한 인터페이스로 만든 싱글턴이 아닐시 인스턴스를 Mock로 대체 불가

싱글턴 생성 방식

  • Public Static Final Method
  • Static Factory Method
  • Enum Type Method

어떻게 만드는 것일까?


Public Static Final Method

public class Computer{
    public static final Computer INSTANCE = new Computer();
    private Computer(){ - }

    public void turnOff(){ - }
}

Public 이나 Protected 생성자가 없으면 Computer 클래스는 전체에서 단 '하나'의 인스턴스를 가지고 있다.

예외가 있다. Reflection API인 AccessibleObject.setAccessible을 사용해 Private 생성자 호출이 가능하다.

-> 두 번째 객체를 생성할 당시에 예외 호출을 사용하면 된다.

Reflection API -> 클래스의 구체적인 타입을 얻지 못해도 클래스의 정보에 대해 접근이 가능하다.

장점

  • 싱글턴임이 API에 명확히 표시한다.
  • 간결적이다.

Static Factory Method

public class Computer{
    public static final Computer INSTANCE = new Computer();
    private Computer(){ - }
    public static Computer getInstance(){
        return INSTANCE;
    }

    public void turnOff(){ - }
}

getInstance() 메소드에 모든 호출을 동일한 객체로 참조 변환 Public이나 Protected 생성자가 없기에 다른 인스턴스는 생성 할 수 없다.
INSTANCE 초기화를 위해 Private은 단 '한 번'만 호출한다.

장점

  • API 변경 할 필요가 없다.
    • 싱글턴이 아니게 변경 할 수 있다.
    • thread 별로 다른 인스턴스 넘겨주기가 가능하다.
  • Static을 Static Generic Singleton으로 만들 수 있다.
  • Static Method의 참조를 Supplier로 사용하는 식이다.
    • Computer:: getInstance -> Supplier

Supplier Interface -> 무언가로 반환하는 함수형 인터페이스 -> Lazy Initialization

앞서 두 방식으로 생성된 싱글턴을 직렬화시 단순히 Serializable을 선언하는 것으로 실행되지 않는다.
-> 모든 Instance 필들를 transient 선언하고 readResolve 메소드 제공해야 한다.

이렇게 하지 않으면 새 인스턴스가 생성되고 -> 가짜 Computer가 생성된다.

private Object readResolve(){
    return INSTANCE;
}

Enum Type

public enum Computer{
    INSTANCE;

    public void turnOff(){ - }
}

1번과 비슷하게 더 간결하고, 노력 없이도 직렬화가 가능하다. Enum 방식은 어떤 상황에서도 제 2의 인스턴스가 생성되는 것을 막아준다.

그래도 대부분 enum 방식으로 싱글턴 생성해주는 것이 가장 좋다.

단, 만들려고 하는 싱글턴이 Enum의 클래스를 상속하면 이 방법은 사용 할 수 없다.

728x90
반응형