[Effectiva-Java] Item3 - private 생성자나 열거 타입으로 싱글턴임을 보증하라

imcool2551·2022년 2월 1일
0

Effective-Java

목록 보기
3/8
post-thumbnail

1. 싱글턴이란


싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스다. 인스턴스를 하나만 만들어야 하는 예는 다음과 같다.

  • 무상태(stateless) 객체
  • 설계상 유일해야 하는 컴포넌트

그러나 클래스를 싱글턴으로 만들면 테스트 하기가 어려워진다는 단점이 있다. 인터페이스를 구현해서 만든 싱글턴이 아니고서야 싱글턴 인스턴스를 가짜(mock)로 대체할 수 없기 때문이다.

그러면 싱글턴을 만드는 방식들은 무엇이 있는지 살펴보자.

2. public static final 필드 방식


// 코드 3-1 public static final 필드 방식의 싱글턴 (23쪽)
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
    public void leaveTheBuilding() {}
}

private 생성자로 인스턴스화를 막고 public static final 필드를 통해 인스턴스를 얻을 수 있게 했다.

장점

  • 해당 클래스가 싱글턴임이 API에 명백히 드러난다.
  • 간결하다

단점

  • 권한이 있는 클라이언트가 리플렉션 API인 AccessibleObject.setAccessible 을 통해 private 생성자를 호출하는 건 막을 수 없다. 이런 공격을 방어하려면 생성자를 수정하여 두 번째 객체가 만들어 지려할 때 예외를 던지게 해야한다.

3. 정적 팩터리 방식


// 코드 3-2: 정적 팩터리 방식의 싱글턴
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
    public static Elvis getInstance() { return INSTANCE; }

    public void leaveTheBuilding() {}
}

Elvis클래스의 getInstance() 메서드는 항상 같은 객체의 참조를 반환한다.

장점

  • 추후에 싱글턴이 아닌 객체를 반환하게 변경할 수 있다.
  • 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다. (아이템 30)
  • 정적 팩터리의 메서드 참조(Elvis:getInstance)를 공급자(supplier)로 사용할 수 있다. (아이템 43, 44)

단점

  • 리플렉션 공격을 막지 못한다.

4. enum 방식


// 코드 3-3: 열거 타입의 싱글턴(바랍직한 방법, 25쪽)
public enum Elvis {
    INSTANCE;

    public void leaveBuilding() {}
}

객체의 수를 통제해야 할 때 열거 타입은 가장 바람직한 방법이다.

장점

  • 간결하다
  • 추가 노력 없이 직렬화가 가능하다
  • 직렬화나 리플렉션 공격에서도 제2의 인스턴스가 생기는 일을 완벽히 막아준다.

단점

  • 만들려는 싱글턴이 Enum외의 클래스를 상속하거나 인터페이스를 구현해야 한다면, 이 방법은 불가능하다.

5. 싱글턴 객체의 직렬화


싱글턴 객체를 직렬화 하려면 Serializable을 구현하는 것 만으로는 부족하다. 역직렬화 할 때마다 새로운 인스턴스가 생성되기 때문이다. 싱글턴 객체를 올바르게 직렬화 하려면 모든 인스턴스 필드를 transient으로 선언하고 readResolve() 메서드를 제공해야 한다.

profile
아임쿨

0개의 댓글