생성자나 열거 타입으로 싱글턴임을 보증하라.
private 생성자 + public static final 필드
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
public void leaveTheBuilding() {
System.out.println("Leaving the building.");
}
public void sing() {
System.out.println("Singing");
}
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
인터페이스를 사용할 경우 테스트에 용이하다.
public class Concert {
private boolean lightsOn;
private boolean mainStateOpen;
private IElvis elvis;
public Concert(IElvis elvis) {
this.elvis = elvis;
}
public void perform() {
mainStateOpen = true;
lightsOn = true;
elvis.sing();
}
public boolean isLightsOn() {
return lightsOn;
}
public boolean isMainStateOpen() {
return mainStateOpen;
}
}
public interface IElvis {
void leaveTheBuilding();
void sing();
}
public class MockElvis implements IElvis{
@Override
public void leaveTheBuilding() {
}
@Override
public void sing() {
System.out.println("Test singing.");
}
}
class ConcertTest {
@Test
void perform() {
Concert concert = new Concert(new MockElvis());
concert.perform();
assertTrue(concert.isLightsOn());
assertTrue(concert.isMainStateOpen());
}
}
Concert가 정상적으로 동작하는지 확인하는 테스트에 경우 불필요하게 싱글턴 인스턴스를 참조할 필요 없다. 인터페이스를 사용하여 가짜 객체를 생성 후 싱글턴 인스턴스를 대체 가능하다.
리플렉션을 통한 싱글턴 객체 인스턴스 생성을 막아야 한다.
public class Elvis implements IElvis, Serializable {
public static final Elvis INSTANCE = new Elvis();
private static boolean created;
/**
* 리플렉션을 통한 싱글턴 인스턴스 생성 방지
* @see ElvisReflection#main(String[])
*/
private Elvis() {
if (created) {
throw new UnsupportedOperationException("Can't be created by constructor.");
}
created = true;
}
public void leaveTheBuilding() {
System.out.println("Leaving the building.");
}
public void sing() {
System.out.println("Singing");
}
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
public class ElvisReflection {
public static void main(String[] args) {
try {
// getConstructor: public 생성자만 접근 가능
// getDeclaredConstructor: 선언되어 있는 생성자만 접근 가능
Constructor<Elvis> defulatContConstructor = Elvis.class.getDeclaredConstructor();
// 생성자의 접근 지시자가 private 이므로 접근 하기 위해 설정
defulatContConstructor.setAccessible(true);
Elvis elvis1 = defulatContConstructor.newInstance();
Elvis elvis2 = defulatContConstructor.newInstance();
System.out.println(elvis1 == elvis2);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
인스턴스가 생성되었을때 변경되는 flag인 boolean 타입 created를 통해 생성자를 통한 인스턴스 생성을 방지함으로서 리플렉션을 통해 싱글턴 객체의 인스턴스 생성을 방지할 수 있다.
직렬화-역직렬화를 통한 싱글턴 인스턴스 생성을 막아야 한다.
public class Elvis implements IElvis, Serializable {
public static final Elvis INSTANCE = new Elvis();
private static boolean created;
private Elvis() {
}
public void leaveTheBuilding() {
System.out.println("Leaving the building.");
}
public void sing() {
System.out.println("Singing");
}
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
/**
* 역직렬화할때 사용되는 메서드
* 역직렬화를 통한 싱글턴 인스턴스 생성 방지
* @see ElvisReflection#main(String[])
* @return Elvis
*/
private Object readResolve() {
return INSTANCE;
}
}
public class ElvisSerialization {
// 직렬화 - 역직렬화로 여러 인스턴스를 생성할 수 있어 싱글턴을 보증하지 못한다.
public static void main(String[] args) {
try {
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"));
out.writeObject(Elvis.INSTANCE);
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"));
Elvis elvis = (Elvis) in.readObject();
System.out.println(elvis == Elvis.INSTANCE);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
역직렬화할때 사용되는 메서드인 readResolve 메서드를 오버라이딩함으로서 싱글턴 인스턴스를 반환한다.
정적 팩터리를 제너릭 싱글턴 팩터리로 만들 수 있다.
public class MetaElvis<T> {
private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();
private MetaElvis() {
}
@SuppressWarnings("unchecked")
public static <T> MetaElvis<T> getInstance() {
return (MetaElvis<T>) INSTANCE;
}
public void sing(T t) {
System.out.println(t);
}
public void leaveTheBuilding() {
System.out.println("Leaving the building.");
}
// 제너릭 싱글톤 펙토리를 사용하면 동일한 인스턴스를 원하는 타입으로 형변환이 가능하다.
public static void main(String[] args) {
MetaElvis<String> elvis1 = MetaElvis.getInstance();
MetaElvis<Integer> elvis2 = MetaElvis.getInstance();
}
}
정적 팩터리의 메서드 참조를 공급자(Suppiler)로 사용할 수 있다.
public interface Singer {
void sing();
}
public class SingerImpl implements Singer {
private static final SingerImpl INSTANCE = new SingerImpl();
private SingerImpl() {}
public static SingerImpl getInstance() {
return INSTANCE;
}
@Override
public void sing() {
System.out.println("Hello!");
}
}
public class Concert {
private boolean lightsOn;
private boolean mainStateOpen;
private IElvis elvis;
public Concert(IElvis elvis) {
this.elvis = elvis;
}
public void perform() {
mainStateOpen = true;
lightsOn = true;
elvis.sing();
}
public boolean isLightsOn() {
return lightsOn;
}
public boolean isMainStateOpen() {
return mainStateOpen;
}
// 정적 팩터리의 메서드 참조를 공급자(Supplier)로 사용할 수 있다.
public void start(Supplier<Singer> singerSupplier) {
Singer singer = singerSupplier.get();
singer.sing();
}
public static void main(String[] args) {
Concert concert = new Concert(Elvis.INSTANCE);
concert.start(SingerImpl::getInstance);
}
}
출처 : 백기선 이펙트브 자바 강의
'JAVA' 카테고리의 다른 글
| Effective Java 의존 객체 주입 (0) | 2023.04.13 |
|---|---|
| Effective Java 싱글턴 part2. (feat. 메서드 참조, 함수형 인터페이스, 직렬화) (1) | 2023.04.11 |
| Effective Java 빌더 (0) | 2023.04.07 |
| Effective Java 리플렉션 (0) | 2023.04.06 |
| Effective Java 인터페이스와 정적 메서드 (0) | 2023.04.04 |