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

배상규·2023년 9월 14일
0

이펙티브 자바

목록 보기
3/12
post-thumbnail

싱글턴

싱글턴 이란?

싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 싱글턴의 전형적인 예로는 함수와 같은 무상태 객체, 설계상 유일한 시스템 컴포넌트를 말한다. 하지만 싱글턴 클래스는 타입을 인터페이스로 정의하고 그것을 구현체로 정의한 것이 아니라면 테스트하기 어려워질 수 있다.


싱글턴 생성 방식

1. public static final 필드 방식

public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    	...
    }

    public void leaveTheBuilding() {
        System.out.println("elvis");
    }
}

private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한번만 호출 된다. 예외는 단 한가지, 권한이 있는 클라이언트는 리플렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있는데, 이렇나 공격을 방어하려면 생성자를 수정하여 두번째 객체가 생성되려 할 때 예외를 던져 막을 수 있다.

장점

  • 해당 클래스가 싱글턴임이 API에 명백히 드러남
  • 간결함

2. 정적 팩터리 방식

public class Elvis {

    private static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    	...
    }

    public static Elvis getInstance() {
        return INSTANCE;
    }

    public void leaveTheBuilding() {
        System.out.println("elvis");
    }
}

Elvis.getInstance는 항상 같은 객체의 참조를 반환하므로 Elvis의 유일한 인스턴스임을 보장한다.

장점

  • API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.
    - 스레드별 다른 인스턴스를 넘기게 하기
  • 제네릭 싱글턴 팩터리 메서드로 변경할 수 있다.
  • 정적 펙터리 메서드 참조를 공급자로 사용할 수 있다.
    - Elvis::getInstance를 대신해 Supplier < Elvis>로 사용할 수 있다.
하지만 이러한 장점이 필요하지 않다면 첫 번째 방식을 사용하는 편이 좋다

3. 열거 타입 사용법

public enum Elvis {

    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("elvis");
    }
}

가장 바람직한 방식은 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 것이 가장 좋은 방법이다.
public 필드 방식과 비슷하지만 간결하고, 직렬화 상황이나 리플렉션 공격에도 방어가 가능하다.

단, 만드려는 싱글턴이 Enum외의 클래스를 상속해야 한다면 이방법은 사용할 수 없다.


싱글턴 클래스를 직렬화할 때 주의할 것

첫 번째, 두 번째로 만든 싱글턴 클래스를 직렬화 하려면 단순히 Serializable을 구현하는 것 외, 인스턴스 필드를 일시적 이라고 선언하고, readResolve 메서드를 제공해야만 한다.

private Object readResolve throws ObjectStreamException {
    return INSTANCE;
}
profile
기록에 성장을

0개의 댓글