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); // 정적 팩토리 메서드를 공급자로 전달
장점
- 유연성 향상
- 지연 초기화
- 테스트 용이성
- 코드 재사용성
위의 싱글턴 방식을 사용하였을때 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어진다는게 무슨 말인가..??
말그대로 싱글턴 객체를 직렬화하고 역직렬화했을때 객체가 새로운 인스턴스로 생성된다는 의미이다.
// 직렬화 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...