티스토리 뷰

(Effective Java) 규칙3. private 생성자나 열거 타입으로 싱글턴임을 보증하라


싱글턴


  • 싱글턴은 객체를 하나만 만들 수 있는 클래스
    • 보통 유일할 수밖에 없는 시스템 컴포넌트 (window manager, file system)
  • But, 클래스를 싱글턴으로 만들면 클라이언트를 테스트하기가 어려워짐
    • 싱글턴이 어떠한 인터페이스의 구현체가 아니라면 가짜 구현이 어려움

싱글턴 구현 방법


  • 싱글턴을 구현하는 방법 (JDK 1.5 이전)

    1. public final 필드를 이용한 방법

       //public final 필드를 이용한 싱글턴
       public class Elvis {
               public static final Elvis INSTANCE = new Elvis();
               private Elvis() { ... }
      
               public void leaveTheBuilding() { ... }
       }
      • 특징
        • private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화 할때 한 번만 호출됨
      • 주의할 점
        • AccessibleObject.setAccessible 메서드의 도움을 받아 권한을 획득한 클라이언트는 리플렉션(reflection) 기능을 통해서 private 생성자를 호출할 수 있음
    2. 정적 팩토리를 이용하는 방법

       //정적 팩터리를 이용한 싱글턴 패턴
       public class Elvie {
               private static final Elvis INSTANCE = new Elvis();
               private Elvis() { ... }
               public static Elvis getInstance(){ return INSTANCE; }
      
               public void leaveTheBuilding() { ... }
       }
      • 특징
        • Elvis.getInstance는 항상 같은 객체에 대한 참조를 반환함 (물론 앞서 말한 공격은 여전히 가능함)
        • public 필드를 사용하면 클래스가 싱글턴인지는 선언만 보면 금방 알 수 있어서 좋음
        • 그러나, 최신 JVM에서는 정적 팩터리 메서드 호출을 항상 inline으로 처리하기 때문에 성능상 차이는 없음
      • 장점
        • API를 변경하지 않고도 싱글턴 패턴을 포기할 수 있다는 것임
          • 스레드 마다 객체를 넘겨주도록 쉽게 수정이 가능
        • 제네릭 타입을 수용하기 쉬움
          • 그러나, 이런 장점이 필요하지 않은 경우도 많음 (그럴때는 public final 필드를 사용 하도록)

싱글턴 클래스의 직렬화


  • 싱글턴 클래스를 직력화 가능(Serializable) 클래스로 만들려면 implements Serializable을 추가하는 것만으로는 부족함

  • 싱글턴 특성을 유지하려면 모든 필드를 transient로 선언하고 readResolve 메서드를 추가해야 함

    • 그렇지 않으면 역직렬화 다 새로운 객체가 생기기 때문임

        // 싱글턴 상태를 유지하기 위한 realResolve 구현 
        private Object readResolve() { 
            // 동일한 Elvis 객체가 반환되도록 하는 동시에, 가짜 Elvis객체는 GC가 처리하도록 만든다. 
            return INSTANCE; 
        }

결론


  • 원소가 하나뿐인 enum 자료형이야말로 싱글턴을 구현하는 가장 좋은 방법임

  • 싱글턴 구현하는 방법(JDK 1.5 부터)

    //Enum 싱글턴 - 이렇게 하는 것이 더 좋다.
      public enum Elvis {
     INSTANCE;
    
          public void leaveTheBuilding() { .. };
      }
    • 특징
      • public 필드를 사용하는 구현법과 동등함
      • 간결하고 직렬화가 자동으로 처리됨. 또한 리플렉션을 통한 공격에도 안전함

끝으로

이 글이 도움이 되었다면, Google 광고 한번씩 클릭 부탁 드립니다. 🙏🙏🙏

광고 클릭은 많은 힘이 됩니다!

반응형
댓글