Java

[Effective Java] 2-3. 객체 생성과 파괴: private 생성자나 열거 타입으로 싱글턴임을 보장하라

noahkim_ 2024. 12. 27. 03:05

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.

 

1. 싱글턴

  • 특정 클래스의 인스턴스를 오직 하나만 생성하는 디자인 패턴

 

특징

무상태 객체
  • 함수

 

설계상 유일해야 하는 객체
  • 시스템 컴포넌트

 

2. 생성 방식

인스턴스 필드

static final 필드값이 초기화됨
public class Yundle {
    public static final Yundle INSTANCE = new Yundle();
    private Yundle() {}

    public static void main(String[] args) {
        Yundle yundle = Yundle.INSTANCE;
    }
}

 

정적 팩토리

제네릭 활용 가능
public class MetaElvis<T> {
    private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();

    private MetaElvis() {}

    @SuppressWarnings("unchecked")
    public static <T> MetaElvis<T> getInstance() { return (MetaElvis<T>) INSTANCE; }

    public void say(T t ) { System.out.println(t); }
    public void leaveTheBuilding() { System.out.println("Whoa baby, I'm outta here!"); }
         
    public static void main(String[] args) {
        MetaElvis<String> elvis1 = MetaElvis.getInstance();
        MetaElvis<Integer> elvis2 = MetaElvis.getInstance();
        elvis1.say("hello");
        elvis2.say(100);

        // 인스턴스는 같으나 타입은 다름
        System.out.println(elvis1.equals(elvis2)); 
    }
}

 

메서드 참조 사용 가능
public class Concert {
    private Singer singer;

    public Concert() { }
    
    public void start(Supplier<Singer> supplier) {
        Singer singer = supplier.get();
        this.singer = singer;
        singer.sing();
    }

    public static void main(String[] args) {
        Concert concert = new Concert();
        concert.start(Elvis::getInstance);
    }
}

 

열거 타입

새로운 인스턴스가 생기는 일을 완벽히 막아줌
  • 직렬화
    • 열거 타입은 기본적으로 직렬화 가능 (Serializable 구현)
      • 직렬화 시, 새로운 인스턴스가 생성되어 직렬화에 사용됨
    • readResolve() 메서드를 오버라이드
      • 직렬화 시, 새로운 인스턴스가필요할 경우 호출되는 메서드
      • 기존의 인스턴스를 반환하도록 하여 새로운 인스턴스 생성을 막음
  • 리플렉션
    • private 생성자로 구현되어 있음
      • 기본적으로 리플렉션은 private 생성자를 외부에서 호출 할 수 있음
    • 열거 타입은 이를 보호하기 위해 방어 로직이 작성되어 있음

 

인터페이스 구현
  • 테스트 시, mock 객체 생성 가능
  • mockito 라이브러리 활용가능