- 싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말함.
- 싱글턴의 예로는 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트.
- 클래스를 싱글턴으로 만들면 테스트가 어려워 질 수 있음.
-> 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 Mock 구현으로 대체할 수 없기 때문.
- 싱글턴을 만드는 방식은 2가지.
- 두 방식 모두 생성자는 privated으로 생성
- 유일한 인스턴스에 접근할 수 있는 수단으로 public staitic 멤버를 생성해둔다.
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 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.
- 참고
'개발 > Java&Kotlin' 카테고리의 다른 글
[Java] try-with-resources 사용하기 (0) | 2023.01.12 |
---|---|
[Java] private 생성자로 인스턴스화 막기, 의존 객체 주입 사용하기 (2) | 2023.01.09 |
[Java] 생성자 대신 빌더 사용하기 (0) | 2023.01.05 |
[Java] 생성자 대신 정적 팩토리 메소드 사용하기 (1) | 2023.01.01 |
[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (7) (0) | 2022.12.23 |