[Effective Java] ch1 - item3. private 생성자나 열거타입으로 싱글턴임을 보증하라
ch1.객체의 생성과 파괴
item3. private 생성자나 열거타입으로 싱글턴임을 보증하라
Singleton
은 인스턴스를 오직 하나만 생성할 수 있는 클래스입니다.
1. public static final 필드 방식의 싱글턴
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
}
Reflection
을 사용하여 private 생성자를 호출할 수 있습니다.
리플렉션 API를 이용한 사용을 막으려면 두번째 생성자가 생성되려 할 때 예외를 던지면 됩니다. 코드
public class SingletonReflectionMain {
public static void main(String[] args) {
SingletonField singletonField = SingletonField.INSTANCE;
try {
Constructor<SingletonField> fieldConstructor = SingletonField.class.getDeclaredConstructor();
fieldConstructor.setAccessible(true);
SingletonField singletonField1 = fieldConstructor.newInstance();
SingletonField singletonField2 = fieldConstructor.newInstance();
System.out.println(singletonField);
System.out.println(singletonField1);
System.out.println(singletonField2);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
결과
chapter01.item03.SingletonField@4517d9a3
chapter01.item03.SingletonField@372f7a8d
chapter01.item03.SingletonField@2f92e0f4
Reflection을 이용한 호출 시 예외를 던지도록 수정
코드
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
private static boolean created;
private Singleton1() {
if (created) {
throw new IllegalStateException("Singleton already created");
}
created = true;
}
}
결과
Caused by: java.lang.IllegalStateException: Singleton already created
at chapter01.item03.SingletonField.<init>(SingletonField.java:12)
... 6 more
직렬화를 사용하여 싱글턴을 깨트릴 수 있습니다.
코드
public class SingletonSerializationMain {
public static void main(String[] args) {
SingletonField singletonField = SingletonField.INSTANCE;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("field.obj"))) {
out.writeObject(SingletonField.INSTANCE);
} catch (Exception e) {
throw new RuntimeException(e);
}
try(ObjectInput in = new ObjectInputStream(new FileInputStream("field.obj"))){
SingletonField singletonField1 = (SingletonField) in.readObject();
System.out.println(singletonField);
System.out.println(singletonField1);
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
결과
chapter01.item03.SingletonField@2ef1e4fa
chapter01.item03.SingletonField@4459eb14
readResolve
를 사용하여 직렬화를 사용하여 싱글턴을 깨트리지 않도록 수정
코드
public class SingletonField implements Serializable {
public static final SingletonField INSTANCE = new SingletonField();
private static boolean created;
private SingletonField() {
if (created) {
throw new IllegalStateException("Singleton already created");
}
created = true;
}
private Object readResolve() {
return INSTANCE;
}
}
결과
chapter01.item03.SingletonField@2ef1e4fa
chapter01.item03.SingletonField@2ef1e4fa
2. 정적 팩터리 메서드 방식의 싱글턴
public class Singleton2 {
private static final Singleton2 INSTANCE = new Singleton2();
public static Singleton2 getInstance() {
return INSTANCE;
}
}
3. 열거 타입 방식의 싱글턴
public enum Singleton3 {
INSTANCE;
}
싱글턴 클래스 사용
public class SingletonMain {
public static void main(String[] args) {
Singleton1 singleton1 = Singleton1.INSTANCE;
Singleton2 singleton2 = Singleton2.getInstance();
Singleton3 singleton3 = Singleton3.INSTANCE;
}
}
싱글턴은 테스트하기 어렵다.
클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트(Concert)를 테스트하기 어렵습니다.
아래 Concert
의 perform()
의 lightsOn()
과 mainStateOpen()
을 테스트하고 싶은데
진짜 Elvis
가 노래를 계속 부르게 됩니다.
만약 Elvis
가 외부API와 연동된 비싼 인스턴스라면 더욱 문제가 됩니다.
public class Concert {
private boolean lightsOn;
private boolean mainStateOpen;
private Elvis elvis;
public Concert(Elvis elvis) {
this.elvis = elvis;
}
public void perform() {
mainStateOpen = true;
lightsOn = true;
elvis.sing();
}
public boolean isLightsOn() {
return lightsOn;
}
public boolean isMainStateOpen() {
return mainStateOpen;
}
}
싱글턴은 어떻게 테스트?
인터페이스를 통해 테스트하면 됩니다.
IElvis
를 상속한 Elvis
와 MockElvis
를 만들고
MockElvis
를 통해 테스트하면 됩니다.
public interface IElvis {
void leaveTheBuilding();
void sing();
}
댓글남기기