이펙티브 자바 # item3 private 생성자나 열거 타입으로 싱글턴임을 보증하라

임현규·2022년 12월 31일
0

이펙티브 자바

목록 보기
3/47
post-thumbnail

단순 싱글턴

싱글턴이란 오직 하나의 인스턴스를 보장하는 방식을 이야기한다.
싱글턴 클래스의 예이다.

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

    // ....
}

위 코드는 Elvis INSTANCE를 초기화할때 한번만 인스턴스를 초기화한다.
다만 private 생성자를 reflection을 활용해 호출할 수 있기는 하다.

정적 팩토리 싱글턴

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

    // ....
}

정적 팩토리 메서드를 활용하는 방법도 있다. 이 방법의 장점은 유연하게 API를 바꾸지 않고 인스턴스를 싱글턴이 아닌 다른 방식으로 변경이 가능하다는 점, Supplier를 활용하여 Elvis::getInstance 와 같은 형태로 사용할 수 있다.

싱글턴 serializable 구현하기

https://madplay.github.io/post/what-is-readresolve-method-and-writereplace-method
https://madplay.github.io/post/what-is-readobject-method-and-writeobject-method

이 두 주소에 싱글턴 직렬화와 역직렬화에 대해 잘 정리되어 있다.

싱글턴의 경우 단순 serializable만 해주어서는 역직렬화시 동일 객체가 생성되지 않는다. 한 번 살펴보자

싱글턴 역직렬화시 싱글턴 보장되지 않는 케이스

public class Elvis implements Serializable {

    private static final Elvis INSTANCE = new Elvis();

    private final String name = "Elvis";

    public static Elvis getInstance() {
        return INSTANCE;
    }

    private Elvis() {
    }

    public String getName() {
        return name;
    }
}
class SerializationTester {

    public static byte[] serialize(Object instance) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (bos; ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(instance);
        } catch (Exception e) {
            // ... 구현 생략
        }
        return bos.toByteArray();
    }

    public static Object deserialize(byte[] serializedData) {
        ByteArrayInputStream bis = new ByteArrayInputStream(serializedData);
        try (bis; ObjectInputStream ois = new ObjectInputStream(bis)) {
            return ois.readObject();
        } catch (Exception e) {
            // ... 구현 생략
        }
        return null;
    }
}
class ElvisTest {

    @Test
    @DisplayName("직렬, 역직렬화시 객체가 일치 테스트")
    void singletonSerializeAndDeSerializeInstanceTest() {
        byte[] serialize = SerializationTester.serialize(Elvis.getInstance());
        Elvis deserialize = (Elvis) SerializationTester.deserialize(serialize);
        assertThat(deserialize).isEqualTo(Elvis.getInstance());
    }

    @Test
    @DisplayName("직렬, 역직렬화시 값 일치 테스트")
    void singletonValueEqualTest() {
        byte[] serialize = SerializationTester.serialize(Elvis.getInstance());
        Elvis deserialize = (Elvis) SerializationTester.deserialize(serialize);
        assertThat(deserialize.getName()).isEqualTo(Elvis.getInstance().getName());
    }
}

싱글턴 역직렬화시 싱글턴 보장되는 케이스

public class Elvis implements Serializable {

    private static final Elvis INSTANCE = new Elvis();

    private final String name = "Elvis";

    public static Elvis getInstance() {
        return INSTANCE;
    }

    private Elvis() {
    }

    public String getName() {
        return name;
    }

    // 역직렬화 싱글턴 보장하기 위해 추가
    private Object readResolve() {
        return INSTANCE;
    }
}

enum 싱글턴

이론상 가장 완벽한 싱글턴이다. 위의 싱글턴과 다르게 직렬화 역직렬화 과정에서도 새로운 인스턴스를 생성하는 과정을 막아준다. 그러나 enum 외에 다른 클래스를 상속해야 하는 경우 사용할 수 없다.

profile
엘 프사이 콩그루

0개의 댓글