티스토리 뷰

싱글턴(singleton)

 오직 하나의 인스턴스로 구성된 클래스를 의미한다. 싱글턴은 보통 무상태(stateless) 객체 또는 설계상 유일하게 사용하는 시스템 컨포넌트를 구현할 때 사용한다.


싱글턴 구현방법 1. public static 멤버를 final 필드로 선언

public class WorkScheduler {
    public static final WorkScheduler INSTANCE = new WorkScheduler();

    private WorkScheduler() {}

    public void resetSchedule() {}
}

 위 코드에서 private 생성자는 public static final 필드인 INSTANCE 를 초기화할 때 딱 한 번 호출된다. 외부로 노출되는 다른 생성자는 존재하지 않으므로, 클래스 초기화 시 할당한 인스턴스가 전체 시스템에서 하나임이 보장된다.

 

 이 방식은 API 스펙으로 해당 클래스가 싱글턴임을 간결하게 표현할 수 있다는 장점이 있다. 또한 final 키워드에 의해 INSTANCE 변수의 재할당을 방지할 수 있다.

싱글턴 구현방법 2. 정적 팩토리 메서드 활용

public class WorkScheduler {
    private static final WorkScheduler INSTANCE = new WorkScheduler();

    private WorkScheduler() {}
    public static WorkScheduler getInstance() {
        return INSTANCE;
    }

    public void resetSchedule() {}
}

 위 코드에서 getInstance() 메서드는 항상 같은 객체의 참조값을 반환함으로써 싱글턴을 유지한다.

 

 이 방식은 언제든지 API 스펙의 변경 없이 해당 객체를 싱글턴이 아니게 변경할 수 있다는 장점이 있다. 의도에 따라 정적팩토리 메서드 로직을 수정해주기만 하면 되는 것이다. 또한 정적 팩토리를 제네릭 싱글턴 팩토리로 만들 수 있다는 것과 메서드 참조를 공급자로 사용할 수 있다고 한다. (각 아이템30, 아이템 43·44) 이 내용에 대해서는 추후 포스팅에서 다뤄보도록 하겠다.

 

 상기와 같은 장점이 필요하지 않다면 1번 public 필드 방식이 더 좋다.

 

 구현방법 1, 2 방식은 Serializable 인터페이스와 같이 직렬화 구조를 구현할 때, 역직렬화에 의해 가짜 인스턴스가 생성되는 것을 방지하기 위한 코드를 작성해 주어야 한다.

private Object readResolve() {
	return INSTANCE;
}

 역직렬화는 결국 본래의 자기 자신을 리턴하는 것과 같다. 위와 같이 역직렬화 인스턴스를 리턴하기 위한 readResolve 메서드를 재정의 한 후, 싱글턴 인스턴스를 리턴해주면 된다. 역직렬화 메커니즘에 의해 생성된 가짜 인스턴스는 가비지 컬렉션 대상으로 인식되어 메모리에서 정리된다.

 

 구현방법 1, 2 방식은 클래스 초기화 시 할당한 인스턴스를 프로그램 종료 시 까지 유지하면서, 객체의 불변성을 유지한다. 한 가지 예외상황에 의해 인스턴스에 변경이 일어날 수 있는데, 리플렉션 API의 AccessibleObject.setAccessible 를 활용해 private 생성자 호출이 가능하다. 이렇게 리플렉션은 남용할 경우, 객체지향 개념의 근간을 흔들만큼 위험할 수 있기 때문에 사용에 유의해야 한다. 이와 같은 현상을 방지하기 위해서는 생성자에서 두 번째 인스턴스의 생성을 감지해 예외를 던져주면 된다.

싱글턴 구현방법 3. 열거타입 방식의 싱글턴

 원소가 하나인 열거타입을 정의해 싱글턴을 구현하는 방법도 있다.

public enum WorkScheduler {
    INSTANCE;

    public void resetSchedule() {}
}

 구현방법 1의 public 필드 타입과 비슷한 형태이다. 상대적으로 코드가 훨씬 간결해지며, 직렬화나 리플렉션 동작 상황에서도 인스턴스의 불변성을 유지해 준다는 큰 장점이 있다. class와 enum은 설계컨셉이 다르기 때문에 부자연스러워 보일 수는 있다. 그러나 대부분의 상황에서는 원소가 하나뿐인 열거타입이 싱글턴을 만드는 가장 좋은 방법이다.

 

 enum 키워드에 의해 선언한 모든 열거형 클래스는 java.lang.Enum 클래스를 상속한다. 이와 같은 이유로 선언하고자 하는 싱글턴 클래스가 다른 클래스를 상속해야 하는 경우, 이 구현방법은 사용할 수 없다. 열거타입이 다른 인터페이스를 구현하는 것은 괜찮다.


마치며

 싱글턴의 구현방법에서 enum을 활용해 볼 수 있다는 생각은 하지 못했던 것 같다. enum을 활용한 싱글턴 구현방식의 장점을 생각해보니, 싱글턴의 필요성(무상태 객체, 시스템 유틸)에 대한 고민을 충분히 하지 않았다는 생각이 든다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday