생성자나 열거 타입으로 싱글턴임을 보증하라.
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);
    }
}

 

출처 : 백기선 이펙트브 자바 강의

+ Recent posts