개발/Java&Kotlin

[Java] private 생성자나 열거 타입으로 싱글턴 보증하기

devhooney 2023. 1. 7. 01:58
728x90

- 싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말함.

- 싱글턴의 예로는 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트.

- 클래스를 싱글턴으로 만들면 테스트가 어려워 질 수 있음.

-> 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 Mock 구현으로 대체할 수 없기 때문.

 

- 싱글턴을 만드는 방식은 2가지.

- 두 방식 모두 생성자는 privated으로 생성

- 유일한 인스턴스에 접근할 수 있는 수단으로 public staitic 멤버를 생성해둔다.

 

 

 

728x90

 

 

 

1. public static 멤버가 final 필드인 방식

public class Member {
    public static final Member INSTANCE = new Member();
    private Member() {
    	...
    }
    public void leaveThePage() {
    	...
    }
}

- private 생성자는 PUblic static final 필드인 Member.INSTANCE를 초기화할 때 딱 한번만 호출됨.

- public이나 Protected 생성자가 없으므로 Member 클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 하나뿐임이 보장됨.

- 위 코드의 첫 번째 장점은 클래스가 싱글턴인 것이 API에 명백히 드러남

- public static 필드가 final이니 절대로 다른 객체를 참조할 수 없다.

- 위 코드의 두 번째 장점은 간결함

 

 

2. 정적 팩토리 방식

public class Member {
    private static final Member INSTANCE = new Member();
    private Member() {
    	...
    }
    public static Member getInstance() {
    	return INSTACNE;
    }
    public void leaveThePage() {
    	...
    }
}

- Member.getInstance는 항상 같은 객체의 참조를 반환하기 때문에 두 개의 인스턴스는 만들어지지 않는다.

- 위 코드의 첫 번째 장점은 API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.

-> 유일한 인스턴스를 반환하던 팩토리 메소드가 호출하는 스레드별로 다른 인스턴스를 넘겨주게 할 수 있다.

- 두 번째 장점은 원한다면 정적 팩토리를 제네릭 싱글턴 팩터리로 만들 수 있다.

- 세 번째 장점은 정적 팩토리의 메소드 참조를 공급자로 사용할 수 있다.

-> Member::Instance를 Supplier<Member>로 사용하는 방식

 

이러한 장점이 아니라면 1번째 방식으로 싱글턴을 보증할 것.

 

 

3. 싱글턴 클래스를 직렬화 하기

- 위 방식으로 만든 싱글턴 클래스를 직렬화하려면 Serializable 선언으로는 부족함

- 모든 인스턴스에 transient를 선언하고, readResolve 메소드를 제공해야 함.

-> 이렇게 하지 않으면 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 생성됨.

 

2번째 방식으로 싱글턴을 만들 때 가짜Member가 만들어지지 않도록 readResolve 메소드 생성

public class Member {
    private static final Member INSTANCE = new Member();
    private Member() {
    	...
    }
    public static Member getInstance() {
    	return INSTACNE;
    }
    public void leaveThePage() {
    	...
    }
    // 싱글턴임을 보장해주는 readResolve 메소드
    private Object readResolve() {
		// 진짜 Member를 반환, 가짜 Member는 가비지 컬렉터에 맡김.
		return INSTANCE;
    }
}

 

 

4. 열거 타입 선언

public enum Member {
    INSTANCE;
    
    public void leaveThePage() {
    	...
    }
}

- public 필드 방식과 비슷하지만, 더 간결하고, 추가 노력 없이 직렬화할 수 있다.

- 복잡한 직렬화 상황이나 제 2의 인스턴스가 생기는 일을 막아준다.

- 대부분의 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법.

* 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.

 

 

 

 

 

 

 

 

 

- 참고

http://www.yes24.com/Product/Goods/65551284

728x90