Study

싱글톤 객체 생성

부산대보금자리 2022. 11. 8. 15:53

오직 한 인스턴스만 만드는 클래스를 싱글톤이라 부른다.

하지만 싱글톤을 사용하는 클라이언트 코드는 mock으로 교체하는게 어렵기 때문에 테스트 하는게 어렵다.

대표적으로 싱글톤으로 만드는 두가지 방법이 존재한다.

 

방법 1. Final필드

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

    private Elvis(){
    }

}

위 경우 public static으로 외부에서 접근이 가능하지만 생성자가 private이므로 단 하나의 객체를 갖게 된다.

하지만 리플렉션을 사용한 방법을 통해 여러 객체가 생성될 여지가 있다.

 

방법 2. Static팩토리 메소드

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

    private Elvis_(){

    }

    public static Elvis_ getInstance(){
        return INSTANCE;
    }
}

 

방법 1과 다르게 객체를 private static으로 선언을 하였기에 외부에서 static 메소드를 통하여 객체를 얻어갈 수 있게 된다.

또한 API를 변경하지 않고로 싱글톤으로 쓸지 안쓸지 변경할 수 있다. 처음엔 싱글톤으로 쓰다가 나중엔 쓰레드당 새 인스턴스를 만든다는 등 클라이언트 코드를 고치지 않고도 변경할 수 있다. 필요하다면 Generic 싱글톤 팩토리를 만들 수도 있다.

 

직렬화(Serialization)

위에서 살펴본 두 방법 모두, 직렬화에 사용한다면 역직렬화 할 때 같은 타입의 인스턴스가 여러개 생길 수 있다. 그 문제를 해결하려면 모든 인스턴스 필드에 transient를 추가(직렬화 하지 않겠다)하고 readResolve메소드를 다음과 같이 구현하면 된다.

 

* 직렬화란?

 

데이터 직렬화 : 메모리를 디스크에 저장하거나, 네트워크 통신에 사용하기 위한 형식으로 변환하는 것

데이터 역직렬화 : 디스크에 저장한 데이터를 읽거나, 네트워크 통신으로 받은 데이터를 메모리에 쓸 수 있도록 변환하는 것

 

1) 직렬화가 필요한 이유

위에서 직렬화는 저장 및 통신을 위한 형식으로 바꾸기 위함이라고 하였다. 이를 위해 직렬화가 필요한 것인데 왜 그런것일까?

데이터의 메모리 구조는 크게 두가지로 나뉜다.

- 값 형식 데이터

int, float, char등 값 형식 데이터는 스택에 메모리가 쌓이고 직접 접근이 가능하다.

- 참조 형식 데이터

객체와 같은 참조 형식 변수를 선언하면 힙에 메모리가 할당되고, 스택에서는 이 힙 메로리를 참조하는 구조로 되어 있다.

 

참조 형식은 값이 아닌 주소가 들어가므로 이대로 저장 및 전송을 할 수 없다. 

직렬화를 수행하면 각 주소 값이 가지는 데이터를 전부 끌어 모아서 값 형식 데이터로 변환해 준다.

직렬화가 된 데이터는 언어에 따라서 텍스트 또는 바이너리 등의 형태가 되는데, 비로소 유의미한 데이터라 부를 수 있다.

 

역직렬화는 이렇듯 바이트로 변환된 데이터를 다시 객체로 변환하는 기술이다.

위에서 언급하는 싱글톤과 관련된 역직렬화에서는 데이터 -> 객체 과정에서 싱글톤이 깨질수 있다는 의미이다.

 

Enum

 

public enum Elvis {
    INSTANCE;
}

Enum을 활용하면 직렬화 문제, 리플렉션 문제 등의 해결할 수 있다. 

 

public enum SingletonEnum {
    INSTANCE;
    int value;
    
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}
public class EnumDemo {
    
    public static void main(String[] args) {
        SingletonEnum singleton = SingletonEnum.INSTANCE;
        
        System.out.println(singleton.getValue());
        singleton.setValue(2);
        System.out.println(singleton.getValue());
    }
}