[이펙티브 자바.아이템3] Private 생성자나 열거 타입으로 싱글턴임을 보증하라

박상준·2024년 5월 19일
0

이펙티브 자바

목록 보기
3/16

싱글톤 패턴 구현 방식

  • 싱글톤이란?
    • 인스턴스를 오직 하나만 생성할 수 있는 클래스를 의미한다.
    • 전형적인 예시
      1. 함수
      2. 무상태(stateless) 객체나 설계상 유일해야 하는 시스템 컴포넌트가 예시가 될 수 있음.
    • 문제점
      • 클래스를 싱글톤으로 만들면 이를 사용하는 클라이언트를 테스트하기 어려워질 수 있다.
      • 싱글톤 인스턴스는 MOCK 구현으로 대체할 수 없기 떄문이다.

싱글톤을 만드는 방식 3가지

1. public static final 필드 방식

public class Elvis {
    //Elvis.INSTANCE 를 초기화시에 `private` 생성자아 딱 한 번만 호출되어 인스턴스가 하나만 생서됨을 보증할 수 있다.
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    public void leaveTheBuilding() {
    }
}
public class Main {
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();

        Elvis elvis2 = new Elvis(); // 생성자가 private 이라 호출 불가능
    }
}

  • 둘다 같은 객체를 가리기고있음을 알 수 있다.

  • JVM 에서 해당 객체는 어떤식으로 메모리를 할당받는걸까?

    1. 클래스가 처음 로드될 떄 , JVM 은 Elvis 클래스의 INSTANCE 필드를 초기화 한다.
    2. Elvis.INSTANCE 는 정적 필드이기에, JVM 의 메서드 영역에 저장된다.
    3. new Elvis() 는 힙 영역에 Elvis 객체를 생성한다.
    4. 이후에 Elvis.INSTANCE 를 호출하는 경우 메모리의 같은 객체를 참조한다.
  • 같은 객체를 가리키는 이유 와 생성자가 왜 계속 호출되지 않는지?

    1. public static final Elvis INSTANCE = new Elvis() 는 클래스가 로드될 때 한 번만 실행됨.
    2. 이후에 INSTANCE 를 통해 같은 객체를 재사용하게 됨.

2. 정적 팩토리 메서드 방식

public class ElvisStaticFactory {
    private static final ElvisStaticFactory INSTANCE = new ElvisStaticFactory();

    private ElvisStaticFactory() {
    }

    public static ElvisStaticFactory getInstance() {
        return INSTANCE;
    }

    public void leaveTheBuilding() {
    }
}

  • 위 예제와 다른 점은 static final 이 private 으로 선언되어 있고
  • 정적 팩토리 메서드로 해당 객체 자체를 반환받도록 설정되어 있다 는 점만 다르다
  • 장점
    1. 나중에 싱글턴이 아니도록 변경할 수 있다

      public class Elvis {
          private static Elvis instance;
      
          private Elvis() {
              // private 생성자
          }
      
          public static Elvis getInstance() {
              if (instance == null) {
                  instance = new Elvis();
              }
              return instance;
          }
      
          public void leaveTheBuilding() {
              System.out.println("Elvis has left the building!");
          }
      }
      • 에서 만약에 instance 가 null 인 경우 새로운 객체를 생성하도록 기존의 로직을 건드리지 않고, 수정이 가능하다.
    2. 정적 팩토리를 제네릭 싱글턴 팩토리로 만들 수 있다.

      • gpt 에 의하면..
        public class SingletonFactory {
            private static final Map<Class<?>, Object> instances = new HashMap<>();
        
            private SingletonFactory() {
                // private 생성자
            }
        
            @SuppressWarnings("unchecked")
            public static <T> T getInstance(Class<T> clazz) {
                return (T) instances.computeIfAbsent(clazz, SingletonFactory::createInstance);
            }
        
            private static <T> T createInstance(Class<T> clazz) {
                try {
                    return clazz.getDeclaredConstructor().newInstance();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        
        • 처럼 어떤 싱글톤이든 하나의 팩토리 클래스에서 다양한 타입의 싱글톤 인스턴스를 생성하고 관리할 수 있다고 한다.

3. 원소가 하나인 열거 타입을 사용하는 방식

public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() { ... }
}

  • Enums 의 경우 INSTANCE 필드는 내부적으로 public static final 로 선언된다.
    • 싱글톤 방식과 결국에는 거의 동일한 구조라고 볼 수 있다.
    • 다만, 코드가 좀 더 간결해지고 가독성이 좋아진다는 장점이 있다.

profile
이전 블로그 : https://oth3410.tistory.com/

0개의 댓글