Singleton using private constructor and Enum type

jiho·2021년 5월 23일
0

EffectiveJava

목록 보기
4/12

Effective Java 3판의 Private 생성자나 열거 타입으로 싱글턴임을 보장하라 Item 3을 참고해서 Singleton을 정리해보겠습니다.

Singleton as Design Pattern

다른 디자인 패턴은 몰라도 대부분의 개발자는 Singleton이 무엇인지는 잘 알고 있을 것이라 생각합니다.

오직 하나의 인스턴스만을 생성할 수 있는 클래스를 말합니다.

프로그램상 하나의 인스턴스만 있도록 만들어진 클래스의 경우 주의할 점은 당연히 또 다른 인스턴스가 생성되는 것을 막는 것입니다. 어떻게 개발자가 잘 구현하냐에 따라 결과는 달라질 것입니다. 하지만 어떻게 싱글턴을 구현해야하는지는 대부분 잘 알려져왔고 이번에 그 내용들을 정리해보려고 합니다.

싱글턴을 만드는 방법 1 - public static final

앞으로 소개할 방버 둘 다 생성자는 private 접근 제어자를 사용해서 감춰두고 인스턴스를 접근할 수 있는 유일한 수단으로 public static 맴버를 남겨둡니다.

우선 public static 맴버를 final 필드로 남겨두는 방식을 살펴보겠습니다.

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

private 생성자는 Elvis.INSTANCE를 초기화할 때 딱 한 번만 호출됩니다. 그 후 public 이나 protected 생성자가 없기 때문에 더이상 instance는 생성되지 않으므로 전체 시스템에 인스턴스가 하나임이 보장됩니다. 예외가 하나 있다면 리플렉션 API를 사용하면 private 생성자를 호출할 수 있긴하지만 생성자에서 두번째 인스턴스가 생성될 때 예외를 던지도록하면 방어할 수 있습니다.

싱글턴을 만드는 방법 2 - 정적 팩터리 메서드

앞선 방법과 마찬가지로 Elvis.INSTANCE 맴버를 static final로 사용하지만 해당 맴버를 private으로 두어 외부로 노출시키지않습니다. 대신 외부에서 해당 인스턴스를 접근하기 위해 정적 팩토리 메서드를 정의합니다.

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

Elivs.getInstace는 항상 같은 객체의 참조를 반환하므로 제2의 Elivs 인스턴스란 결코 만들어지지 않을 것입니다. (하지만 마찬가지로 리플렉션 API 를 사용할 경우, 생성가능합니다.)

이러한 정적 팩토리 메서드 방식의 첫번째 장점은 클라이언트에게 노출된 API를 바꾸지않고 싱글턴이 아니게 변경 가능합니다. 스레드 별로 새로운 인스턴스를 넘겨주는 구현으로 손쉽게 변경가능합니다.

두 번째 장점은 원한다면 아래와 같이 정적 팩토리를 제네릭 싱글턴 팩터리로 만들 수 있습니다.

public static <T> T getInstance() {
	return (T) INSTANCE;
}

세 번째 장점은 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용 할 수 있다는 점이다. Elvis::getInstanceSupplier<Elivis>로 사용하는 식입니다.

Supplier interface는 자바8에 소개된 인자를 받지않고 결과를 리턴하는 함수형 인터페이스입니다.

위 두 방식 모두 private 생성자를 두고 불변 정적 변수에 인스턴스를 할당하는 방식이라 역직렬화를 할 때마다 새로운 인스턴스가 생성되는 것은 막을 수 없습니다. 이럴 경우 가짜 instance가 생성되는 것을 막고 싶을 경우, readResolve 메서드를 추가하면됩니다.

readResolve는 역직렬화 과정에 스트림으로 부터 읽려진 object를 처리할 수 있습니다.

private Object readResolve() {
	return INSTANCE;
}

이번에는 마지막으로 Effectvie Java 에서 가장 추천하는 방법을 적용해보겠습니다.

싱글턴을 만드는 방법3 - 열거 타입 방식의 싱글턴

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

public field 방식과 비슷하지만 직렬화관련된 이슈도없고 더간결합니다. 심지어 리플렉션 관련된 문제도 없습니다. class 로 인스턴스를 만들어왔던 방식에 비해 조금 부자연스러워보이지만 대부분 상황에서는 원소가 하나 뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법입니다. 단, 상속을 해야한다면 이 방법을 사용하기는 어려워보입니다.

profile
Scratch, Under the hood, Initial version analysis

0개의 댓글