이펙티브 자바:: 아이템 3 <private 생성자나 열거 타입으로 싱글턴임을 보증하라>
🙊

이펙티브 자바:: 아이템 3 <private 생성자나 열거 타입으로 싱글턴임을 보증하라>

Created
Aug 11, 2024 08:36 AM
Last edited time
Last updated August 22, 2024
Tags
Language
Language
Java
URL

Intro::

이펙티브 자바 정리본입니다.
 
싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말합니다. 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워 질 수있습니다. 왜냐하면 타입을 인터페이스로 정의해 그 인터페이스를 구현한 싱글턴이 아닌이상 mock 구현으로 대체할 수 없기 때문입니다.
 

싱글턴 만드는 방식

public static final 필드 방식의 싱글턴

생성자를 private으로 감추고 유일한 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 하나 마련해둡니다.
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { }// INSTANCE 초기화 할때 한번 불러진다. public void leaveTheBuilding() { System.out.println("Whoa baby, I'm outta here!"); } }

정적 팩터리 메서드 방식의 싱글턴

정적 팩터리 메서드를 public static 멤버로 제공
public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis() { } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding() { System.out.println("Whoa baby, I'm outta here!"); } }
 
위 두방식의 경우 리플렉션 API인 AccessibleObject.setAccessible을 이용해 private 생성자를 호출할 수 있습니다. 이러한 공격을 방어하려면 생성자를 수정하여 두번째 객체가 생성되려 할 때 예외처리하면 됩니다.

열거 타입 방식의 싱글턴 - 바람직한 방법

대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법입니다.
public enum Elvis { INSTANCE; public void leaveTheBuilding() { System.out.println("기다려 자기야, 지금 나갈께!"); } }
단, 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 해당 방법은 사용할 수 없습니다.

첫번째 방식의 장점

  • API에 싱글턴임이 명백히 들어난다는 점
  • public static 필드가 final이어서 절대로 다른 객체를 참조할 수 없다는 점
  • 간결함.
 

두번째 방식의 장점

  • API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다는 점
  • 원한다면 정적 펙터리를 제네릭 싱글턴 팩터리로 만들수 있다는 점
  • 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다는 점
 

질문

정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다는 점이 무슨 말인가..?
public void process(Supplier<MyClass> supplier) { MyClass instance = supplier.get(); // instance를 사용하여 작업 수행 } // 사용 예시: process(MyClass::getInstance); // 정적 팩토리 메서드를 공급자로 전달
 
장점
  1. 유연성 향상
  1. 지연 초기화
  1. 테스트 용이성
  1. 코드 재사용성
 
위의 싱글턴 방식을 사용하였을때 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어진다는게 무슨 말인가..??
말그대로 싱글턴 객체를 직렬화하고 역직렬화했을때 객체가 새로운 인스턴스로 생성된다는 의미이다.
// 직렬화 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj")); oos.writeObject(Singleton.getInstance()); oos.close(); // 역직렬화 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj")); Singleton newInstance = (Singleton) ois.readObject(); ois.close(); // 새로운 인스턴스가 생성됨 System.out.println(Singleton.getInstance() == newInstance); // false
이를 해결하기 위해 readResolve 메서드를 구현하면 된다.
import java.io.Serializable; public class Singleton implements Serializable { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } // 역직렬화된 인스턴스 대신 기존 인스턴스를 반환 private Object readResolve() { return INSTANCE; } }

References::

이펙티브 자바 / 조슈아 블로크 지음 (프로그래밍 인사이트)

Loading Comments...