Item 3~5. private 생성자 또는 열거타입으로 Singleton 보증하라

2019. 3. 10. 15:45Java/Effective Java

반응형

Singleton

instance를 하나만 생성할 수 있는 클래스
설계상 유일해야하는 system component, 무상태(stateless) 객체 등에 이용됩니다.


Singleton 만드는 방식

  1. public static final field : 생성자는 private, 유일한 인스턴스를 public static 으로 설정.
  2. static factory method : 생성자는 private, 유일한 인스턴스도 private 정의 하며, 정적 팩터리 메서드를 통해 객체에 접근
  3. enum : enum방식을 통해 생성자를 final static 형으로 선언하고 이용.

권장하는 방법은 3번째 방식입니다.


public static final로 정의한 멤버 INSTANCE를 필드명으로 접근하는 방식

생성자는 private화하여 외부에서 객체 생성을 할 수 없도록 만들어서 단 한번만 객체 생성이 되고, 이를 필드명으로 접근하는 방식입니다.

class Car {
	public static final Car INSTANCE = new Car();
	private Car() { }
	public void introduce() {
		System.out.println("안녕하세요.");
	}
}

public class Test {
	public static void main(String[] args) {
		Car car = Car.INSTANCE;
		car.introduce();
	}
}

private 생성자는 클래스가 초기화 될 때 단 한번만 호출됩니다.

하지만, 권한이 있는 client는 리플렉션 API인 AccessibleObject.setAccessible을 사용하여 private 생성자를 호출할 수 있다는 예외상황이 있다

(때문에 3번째 방식인 enum을 통해 보증하는 방식을 권장합니다. )



static factory method를 통해 singleton 객체에 접근하는 방식

class Car {
	private static final Car INSTANCE = new Car();
	private Car() { }
	public static Car getInstance() {
		return INSTANCE;
	}
	public void introduce() {
		System.out.println("안녕하세요.");
	}
}

public class Test {
	public static void main(String[] args) {
		Car car = Car.getInstance();
		car.introduce();
	}
}

첫번째 방식과 같이, 리플렉션을 통한 예외상황이 존재합니다.

그리고, 정적 팩터리 메서드의 경우 reqeust로 들어오는 인자에 따라 반환하는 인스턴스 타입을 각기 다르게 설정할 수도 있고
제네릭 싱글턴 메서드로 설정할 수도 있어, 확장성이 있다.

그러나 이런 특별한 설정이 필요하지 않다면 굳이 2번째 방식을 이용할 필요는 없다.


++

그리고, 1번째와 2번째 방식에서 Serializable을 구현하고자 할 때, Singleton 구조가 유지가 되지 않을 수도 있다.

이런 문제를 해결하기 위해 Singleton임을 보장해주는 메서드인 readResolve 메서드를 정의하는 것이 좋다.

private Car readResolve() {
	return INSTANCE;
}

enum Car {
	INSTANCE;
	public void introduce() {
		System.out.println("안녕하세요.");
	}
}

public class Test {
	public static void main(String[] args) {
		Car car = Car.INSTANCE;
		car.introduce();
	}
}

1번 방식과 유사하나, 보다 간결하게 Singleton을 구현할 수 있고,
뿐만 아니라 추가적인 설정 없이 직렬화할 수 있고. 리플렉션 공격에도 인스턴스가 추가적으로 더 생성하는 예외상황을 완벽히 막아준다.
때문에 대부분의 상황에서 원소가 하나뿐인 열거타입은 Singleton을 만드는데에 가장 좋은 방식이다.

단, 열거 타입은 다른 interface를 implements 할 수 없기 때문에 Enum외의 다른 클래스를 상속해야 한다면 이 방식을 사용할 수 없다.


인스턴스를 만들 필요가 없는,
정적 필드와 정적 메서드만 담긴 클래스를 정의할 때,

다시 말하자면, 객체가 생성될 필요가 없는 클래스를 만들 때에는
객체를 생성할 수 없도록 NoArgsConstructor를 private화 하자.


클래스가 내부적으로 하나의 자원에 의존할 경우라면, Singleton과 정적 유틸리티 클래스 형태로 만드는 것이 좋으나,
만일 상황에 따라 여러 자원을 이용할 거라면, Singleton 구조는 적합하지 않다.

이런 경우에는 다형성 원리를 이용하여 인자 있는 생성자로 factory method 패턴 형태로 구조를 짜는게 좋다.

728x90
반응형