Java

[Effective Java] Item03. private 생성자나 열거 타입으로 싱글턴임을 보증하라

Item03. private 생성자나 열거 타입으로 싱글턴임을 보증하라

 

싱글턴(singleton) : 인스턴스를 오직 하나만 생성할 수 있는 클래스. 보통 무상태(stateless) 객체나 설계상 유일해야 하는 시스템 컴포넌트 등에 사용.

 

 

! public static final 필드 방식의 싱글턴

 

- 생성자를 private으로 설정하여 외부에서 인스턴스를 생성할 수 없게 한다.

- public static final 필드로 new 키워드를 통해 인스턴스를 하나만 생성한다.

- 외부에서는 SingletonClass.INSTANCE를 통해 인스턴스에 접근한다.

 

public class Singleton1 {
    // public static 멤버 변수를 통해 싱글턴 인스턴스 생성
    public static final Singleton1 INSTANCE = new Singleton1();

    // 생성자의 접근제어자를 private으로 설정하여 외부에서 인스턴스의 생성을 막음
    private Singleton1() {
    }
}

장점

- 코드가 간결하다 

- private static final 필드를 확인함으로써 해당 클래스가 싱글턴임이 쉽게 API에 명백히 드러난다.

 

 

! 정적 팩터리 메서드 방식의 싱글턴

 

- 생성자를 private으로 설정하여 외부에서 인스턴스를 생성할 수 없게 한다.

- private static final 필드로 new 키워드를 통해 인스턴스를 하나만 생성한다.

- 위 인스턴스를 return하는 정적 팩터리 메서드를 생성한다. ex) getInstance()

- 외부에서는 SingletonClass.getInstance()를 통해 인스턴스에 접근한다.

 

public class Singleton2 implements Serializable {

    private static final Singleton2 INSTANCE = new Singleton2();

    private Singleton2() {
    }

    // 정적 팩터리 메서드를 통해 같은 instance 반환
    public static Singleton2 getInstance() {
        return INSTANCE;
    }
}

장점 

- API를 바꾸지 않고도 (or 외부 코드를 수정하지 않고도) 싱글턴이 아닌 방식으로 변경할 수 있다. (getInstance() 메서드를 수정하여 스레드별 다른 인스턴스 반환 등의 변경 가능)

- 원한다면 정적 팩터리 메서드를 제네릭 싱글턴 팩터리 메서드로 만들 수 있다. (해당 파트는 공부 후 내용 추가)

- 정적 팩터리의 메서드 참조를 공급자(Supplier) 타입으로 사용할 수 있다. (SingletonClass::getInstance 형태처럼)

 

 

! 위 두 방식 모두 클래스를 역직렬화 할 때마다 새로운 인스턴스가 생성되는 문제가 발생한다.

 

- 이를 해결하기 위해서는 모든 인스턴스 필드를 transient(일시적, 직렬화 하지 않겠다)라고 선언한다.

- readResolve 메서드를 제공해 유일하게 생성된 인스턴스를 반환하도록 한다.

 

public class Singleton2 implements Serializable {

    // 기본적으로 static 필드는 직렬화되지 않음
    private static final Singleton2 INSTANCE = new Singleton2();

    // 역직렬화 시 싱글턴을 보장
    private Object readResolve() {
        return INSTANCE;
    }

    private Singleton2() {
    }

    // 정적 팩터리 메서드를 통해 같은 instance 반환
    public static Singleton2 getInstance() {
        return INSTANCE;
    }
}

 

 

! 더 간결하고, 추가 노력 없이 직렬화가 가능하며, 리플렉션 공격에도 안전한 싱글턴 방식은 원소가 하나뿐인 열거(Enum) 타입이다.

 

- enum 타입에 instance 원소 하나만을 두면 끝!

- 단, enum 타입은 인터페이스 구현은 가능하지만 클래스 상속은 불가하다.

 

public enum Singleton3 {
    INSTANCE;

    public void doSomething() {
        // do something
    }
}

 

* 위 열거 타입은 이상적인 방식이며, Spring을 사용한다면 Spring Bean을 통해 싱글턴 패턴을 사용하는 것이 제일 나을 것 같다.

 

 

소스코드 : https://github.com/HunSeongPark/effective-java/tree/master/src/main/java/item03

 

GitHub - HunSeongPark/effective-java: 책 Effective Java 3/E 공부

책 Effective Java 3/E 공부. Contribute to HunSeongPark/effective-java development by creating an account on GitHub.

github.com