22.04.11 Item3 & Item4 싱글톤, private 생성자

Seunghan·2022년 4월 11일
0

Java

목록 보기
4/6

아이템3. private 생성자나 열거 타입으로 싱글톤임을 보증하라.

싱글톤

인스턴스를 오직 하나만 생성할 수 있는 클래스

  • 함수와 같은 무상태(stateless) 객체

  • 설계상 유일해야 하는 시스템 컴포넌트

  • 싱글톤 클래스는 이를 사용하는 클라이언트를 테스트하기 어렵게 만들 수 있음

  • because 생성자를 private로 감춰 두었기 때문에 mock 구현으로 대체가 어렵기 때문

싱글톤의 생성 방식

private static final 필드 방식

코드가 싱글턴임이 API에 명백하게 드러난다

public class Elvis {
// private 생성자는 private final인 싱글턴 인스턴스를 초기화할 때 단 한번만 호출
    public static final Evlis INSTANCE = new Elvis();
    private Elvis() {...}

    public void leaveTheBuilding() {...}
}

주의!
이 인스턴스가 싱글턴임이 보장되지만 유일하게 예외가 있는데, 리필렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있다.

  • 이를 예방하려면 생성자 차원에서 2번째 객체가 생성되려 할 때 예외를 던지게 하라!

2. 정적 팩터리 방식의 싱글턴

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() {...}
// 동일 객체의 참조를 반환하기에 싱글턴 보장(단, 이 경우에도 리플렉션을 통한 예외는 동일)
    public static Elvis getInstance() {return INSTANCE;}

    public void leaveTheBuilding() {...}
}
  • 장점
    • API를 변경하지 않고도 싱글턴이 아니게 변경 할 수 있다.
    • 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다.
    • 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다. ex ) Elvis::getInstanceSupplier<Elvis>로 사용

Private static final 필드 방식 & 정적 팩터리 방식의 문제점

싱글턴 클래스를 직렬화하려면 단순히 Serializable를 구현하는 것으로는 부족하다.

직렬화 하려면

  • 모든 인스턴스 필드를 일시적(transient)이라 선언해야한다.
  • 또한 readResolve 메서드를 제공해야 함

이렇게 하지 않을 경우 역직렬화할 때 마다 새로운 인스턴스가 만들어진다.

// readResolve 메서드 예시
private Object readResolve() {
    // '진짜' Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡긴다.
    return INSTANCE;
}

3. 열거 타입 방식

원소가 하나인 열거 타입을 선언하는 방식

public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {...}
  • 추가적인 노력 없이 직렬화가 가능
  • 복잡한 직렬화 상황이나 리플렉션 공격에서도 제2의 인스턴스 생성을 완전히 막음

핵심 요약

싱글톤을 보장하는 방식은 3가지가 있다.

  • 대부분의 경우 우너소가 하나뿐인 열거 타입을 사용해 싱글톤을 보장하라!
  • 만약 상속이 필요하다면 private static final 필드 방식 혹은 정적 팩터리 방식을 사용해 싱글톤을 보장하라

아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라

정적 메서드와 정적 필드만을 담은 클래스

인스턴스를 만들어 사용하려고 설계한 클래스가 아니다. 그러므로 혹시라도 인스턴스가 만들어지지 않도록 설계해야한다.

- `java.lang.Math`와 `java.util.Array`: 기본 타입 값이나 배열 관련 메서드를 모아놓음
- `java.util.Collections`: 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리) 모음(자바 8부터는 인터페이스에 넣을 수 있다)
- final 클래스와 관련한 메서드를 모을 경우: final클래스를 상속해서 하위 클래스에 메서드를 넣는 건 불가능하기 때문

추상 클래스로는 인스턴스화를 막지 못한다!

- 추상 클래스를 상속받은 하위 클래스를 만들어서 인스턴스화할 수 있다.
- 추상 클래스는 보통 상속에서 많이 쓰이므로 상속해서 사용하라는 의미로 오독될 수도 있다.

private 생성자를 추가하여 클래스의 인스턴스화를 막아라!
→ 생성자를 막았으므로 상속의 위험 또한 없다!

profile
주니어 백엔드 개발자입니다!

0개의 댓글