Singleton Pattern

수박참외메론·2022년 10월 12일
0

싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.
싱글턴의 전형적인 예로는 stateless한 객체나 설계상 유일해야하는 시스템 컴포넌트 등을 들 수 있다.

Static 에 대한 이해

이 강에서 알아야 될 static 의 개념은 static 으로 정의한 메소드나 멤버들은 runtime 에 heap 영역에 생기는 것이 아닌 해당 클래스가 static 영역에 올라갈 때 같이 올라간다는 점 이다.

싱글턴을 만드는 방식

두가지 방식을 살펴볼 건데 두방식 모두 생성자는 private으로 감춰두고, 유일한 인스턴스에 접근할 수 있는 수단으로 public static 맴버를 하나 마련해둔다.

  1. final 필드 방식
public class Elvis(){
	public static final Elvis INSTANCE = new Elvis();
    private Elvis() {...}
    public void leaveTheBuilding(){...}

이렇게 하면 유일한 Elvis instance는 어느시점에 생성되나?

위의 코드를 원활하게 이해하려면 클래스의 맴버변수의 초기화 에 대해 좀 파악을 해야될 것 같다.

위에서 초기화 해준 방식을 명시적 초기화 방식이라고 하는데 결론적으로는 Elvis클래스가 static 영역에 로딩 될 때 INSTANCE도 같이 초기화된다고 생각하면 된다.
https://staticclass.tistory.com/48

그리고 나서 private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한번 호출되기 때문에, 외부에서 Elvis의 instance는 만들수가 없게 되는 것이다.

그리고 static method를 사용하는 것이 아니라 해당 클래스에 선언된 일반 메서드를 사용하게 될때면 이런식으로 사용할 것이다.

Elvis.Instance.leaveTheBuilding();

사실 리플렉션 API 인 AccessibleObject.setAccessible을 이용해 private 생성자를 호출할 수가 있게 된다는데, 이는 나중에 다루도록 하자.

  1. 정적 패터리 메서드를 public static 맴버로 제공
public class Elvis(){
	private static final Elvis INSTANCE = new Elvis();
    private Elvis() {...}
    public static Elvis getInstance() { return INSTANCE; }
    public void leaveTheBuilding(){...}

첫번째 방식과 똑같은데 정적메소드로 Instance를 반환시키는 모습을 확인할 수 있다.

1방식으로 코드를 구현하면(public 필드 방식) 해당 클래스가 싱글턴임이 API에 명백히 드러나고, final 키워드로 인해 절대로 다른 객체를 참조할 수 없다는 점이다.

2방식으로 코드를 구현하면 마음이 바뀐다면 API 를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다는 점이다. 유일한 인스턴스를 반환하던 팩터리 메서드가 호출하는 스레드별로 다른 인스턴스를 넘겨주게 할 수 있다.

(먼소리지)

2방식의 두번째 장점은 원한다면 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다는 점.
세번째 장점은 정적 팩터리 메서드 참조를 공급자로 사용할 수 있다는 점이다.

직렬화란?

https://go-coding.tistory.com/101

그래서 둘 중 하나의 방식으로 만든 싱글턴 클래스를 직렬화하려면 단순히 serializable을 구현한다고 하는것으로는 부족하다. 직렬화된 인스턴스를 역직렬화할때마다 새로운 인스턴스가 만들어지기 때문에 싱글턴패턴이 망가지게 된다. 그래서 아이템 89 에서 제시하는대로 모든 instance field를 일시적이라고 선언하고, readResolve 메서드를 제공하여 "진짜" Elvis 인스턴스를 반환하도록 한다.

열거 타입 방식의 싱글턴

public enum Elvis {
	INSTANCE;
    
    public void leaveTheBuilding() {...}
}

public 필드 방식과 비슷하지만, 더 간결하고, 추가 노력없이 직렬화할 수 있고, 복잡한 직렬화 상황이나 리플렉션 공격에도 제 2의 인스턴스가 생기는 일을 완벽히 막아준다.
조금 부자연스러워 보일 순 있지만, 대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.

Enum 타입

https://scshim.tistory.com/253

private 생성자

어떤 클래스가 static method, static field만 담고 있다면 굳이 비용을 들여가며 객체를 만들 필요가 없다.

또 위와 같이 singleton 으로 관리되는 객체에 대해서는 생성자를 일부로 안만들어 한 클래스에 대해 단 하나의 객체를 유지하려는 경우도 있다.

이렇듯 client 코드에서 마음대로 해당 클래스의 객체를 안만들게 하고 싶으면 단순히 private 생성자를 선언하여 해당 객체를 할당하지 못하도록 할 수 있다.

abstract class로 선언하면 객체로 안되지 않는가?

그냥 하위클래스를 인스턴스화하면 그만이다.
위와 같이 생성자를 private으로 두게 된다면 상속도 불가능하다. 모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출하게 되는데, 이를 근본적으로 접근하지 못하니 상속이 안된다.

profile
하루하루는 성실하게 인생전체는 되는대로

0개의 댓글