기록공간

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

Java/Effective Java

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

입코딩 2022. 3. 25. 17:02
반응형

싱글턴이란?

싱글턴이란 디자인 패턴에서 생성 패턴 중 하나로 오직 하나의 인스턴스만 생성하도록 약속하는 패턴이다.

보통 싱글턴 패턴은 무상태(Stateless) 객체나 시스템 객체같 유일해야 하는 객체를 생성하는데에 사용된다.

 

싱글턴 패턴의 한계는 그 특성에서도 알 수 있다. 하나의 인스턴스만 생성 가능 하다는 특성 말이다.

싱글턴을 사용하려는 클라이언트를 테스트하기가 어려워 질 수 있다. 왜냐하면 테스트를 위한 가짜(mock)객체를 구현하는 것으로는 싱글턴을 대체 할 수 없기 때문이다. 

 

싱글턴 생성 방법

첫번째 방법public 정적 final 멤버변수를 사용하는 방법이다. 

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    
    public void leaveTheBuilding() { ... }
}

INSTANCE 멤버변수를 생성하여 Elvis 인스턴스를 선언과 동시에 초기화하고 있다. 

생성자를 private으로 두었기 때문에 내부 클래스에서만 접근이 가능하다는 것도 주목해야한다.

이렇게 하면 java는 클래스를 생성과 동시에 초기화 하면서 Elvis의 인스턴스가 딱 한 번 생성된다.

때문에 Elvis는 전체 시스템에서 인스턴스가 하나임을 보장한다.

 

이 방법의 장점은 해당 클래스가 싱글턴임을 명백히 할 수 있다는 것이다.

public static 필드가 final이니 다른 객체가 참조 할 수 없기 때문이다. 

또 다른 장점은 간결하다는 것이다.

 

하지만, 이 방법은 리플렉션 API인 AccesibleObject.setAcceisible() 을 사용하여 private 생성자를 호출할 수 있다.

이러한 공격은 예외 처리를 통해 막을 수 있다.

 

 

두번째 방법정적 팩터리 메서드를 사용하는 방법이다. 

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public static Elvis getInstance() { return INSTANCE; }
    
    public void leaveTheBuilding() { ... }
}

정적 팩터리 메서드를 통해서만 해당 변수를 반환하도록 변경되었다.

멤버변수는 private이므로 INSTANCE에 직접 접근할 수 없다.

 

이 방법의 장점은 API를 바꾸지 않아도 싱글턴이 아니게 변경 할 수 있다.

유일한 INSTANCE를 반환하던 팩터리 메서드를 호출 스레드별로 다른 인스턴스로 넘겨줄 수 있다.

또 다른 장점은 정적 팩터리 메서드를 공급자로 사용 할 수 있다는 것이다.

Elvis::getInstance에서 Supplier<Elvis> 형식으로 변경하면 된다.

 

하지만 이러한 장점들이 필요 없는 경우에는 첫번째 방법을 사용하는 것이 더 바람직하다.

 

 

세번째 방법열거타입 방식의 싱글턴을 사용하는 것이다.

public enum Elvis {
    INSTANCE;
    
    public void leaveTheBuilding() { ... }
}

위의 두 방법보다 훨씬 더 간결하고 추가 노력을 하지 않아도 직렬화가 가능하다.

또한 복잡한 상황에서도 제 2의 인스턴스를 생성하는 상황을 막아준다.

 

단점은 enum 외의 클래스를 상속받은 경우에 사용이 불가능 하다는 것이다.

 

반응형
Comments