[아이템 3] private 생성자나 enum 타입으로 싱글턴임을 보증하라

ajufresh·2020년 2월 23일
0

Effective Java 3/E

목록 보기
3/9
post-thumbnail

블로그에 게시하는 위 글은 전체적인 내용 정리가 아닌
책을 읽으면서 새로 알게된 내용이나 제가 중요하다고 생각하는 내용을 정리한 글입니다.

백기선님 유튜브 (#3 싱글톤을 만드는 여러가지 방법 그중에 최선은?)

싱글턴? (singleton)

인스턴스를 하나만 생성할 수 있는 클래스 (ex. 무상태 객체, 시스템 컴포넌트)
-> new로 객체 생성 불가능 (static 같은걸로 만들어져 있는 객체를 가져다 써야함)
-> mock객체를 만들 수 없기 때문에 테스트가 어렵다.

싱글턴을 만드는 방법

공통으로 둘 다 생성자는 private이며, 접근 방식은 public static 멤버이다.

1. public static 멤버가 final인 방식

public class Elvis{
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {...}
    
    public void leaveTheBuilding() {...}
}
Elvis el = new Elvis(); // 불가능

Elvis el = Elvis.INSTANCE; // 가능
Elvis el2 = Elvis.INSTANCE; // 가능

위 코드처럼 INSTANCE를 호출하게 되면 Elvis는 최초 한 번만 만들어진 이후에
쭉 재사용이 된다.

즉, el과 el2는 같은 객체를 참조하고 있는 상태이다.

단점

리플랙션 API의 setAccecessible()을 통해 private 생성자를 호출 하여,
객체를 중복하여 만들 수도 있다.

이런 경우를 방지하기 위해 아래처럼 생성자를 수정하여야한다.

public class Elvis{
    public static final Elvis INSTANCE = new Elvis();
    
    static int count = 0;
    
    private Elvis() {
    	count++;
        if(count > 1){
        	throw new ...
        }
    }
   
}

2. 정적 팩토리 메소드를 public static 멤버로 제공

public class Elvis{
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() {...}
    
    public static Elvis getInstance() { retrun INSTANCE; }
    
    public void leaveTheBuilding() {...}
}
Elvis el = Elvis.getInstance(); 

여기서 아래 코드처럼 싱글톤이 아닌 형태로 변환을 할 수 있다.
하지만 클라이언트에게는 아무런 영향이 없다.

public class Elvis{
 
    public static Elvis getInstance() { retrun new Elvis(); }

}

또한, static 팩토리 메소드를 Supplier에 대한 메소드 레퍼런스로 사용할 수도 있다.

Supplier<Elvis> el = Elvis::getInstance;

직렬화와 역직렬화

위 두 가지 방법을 사용할 경우에는
역직렬화를 할 때마다 새로운 객체가 생성되기 때문에 이를 방지하기 위해서
모든 인스턴스 필드를 transient로 선언하고,
readResolve 메소드를 추가해야한다.
(readResolve는 Serializable의 숨겨진 메소드이다.)


private static final transient Elvis INSTANCE = new Elvis();


private Object readResolve(){
	return INSTANCE;
}

3. Enum 선언

public enum Elvis{
	INSTANCE; // 인스턴스는 하나만 만들 수 있음

	public String getName() {
		return "sunyoung";
	}

}
String name = Elvis.INSTANCE.getName();

public 필드 방식과 비슷하지만,
리플렉션 공격도 막아주고, 직렬화도 간편하다.

-> 대부분의 상황에서 가장 좋은 방법

싱글턴이 enum외의 클래스를 상속한다면 이 방법은 사용이 불가능하다.
(하지만 인터페이스 구현은 가능)

profile
공블로그

0개의 댓글