- 이 글은 Effective Java 를 기반으로 작성되었습니다.
- 싱글턴
싱글턴은 객체를 하나만 만들 수 있는 클래스이다. 1.5 이전 버전의 JDK 에서는 2 가지 방법으로 싱글턴을 구현할 수 있다. 하나는 정적 멤버를 선언하는 것이고 다른 하나는 정적 팩토리 메소드를 이용하는 것이다. 이 방법으로 어떻게 싱글턴을 구현할 수 있는지 살펴보자.
2 가지 방법은 구현방법이 다르지만 공통점이 있는데 생성자가 private 여야 한다는 것이다. 생성자를 private 로 써야한다니 뭔가 있어 보이지만 생각해보면 객체가 하나만 존재하려면 자기자신 외에 생성자를 호출하면 안되므로 당연한것이다.
- 정적 멤버
첫번째 방법은 정적 멤버 final (static final) 멤버를 초기화하고 이를 외부에 공개하는 것이다.
public class ElvisStaticMember {
private ElvisStaticMember() {}
public static final ElvisStaticMember INSTANCE = new ElvisStaticMember();
}
위에서 public static final 인 의미를 살펴보자. 일단 생성자가 private 이기 때문에 다른 클래스에서 new 로 생성을 할 수 없다. 이 말은 객체를 생성하지 않고 다른 class 에서 INSTANCE 에 접근해야 한다는것인데 이런 상황에서 접근하려면 public static 이어야 한다. 또한 한번 생성한 싱글톤 객체가 변경되지 않도록 final 을 붙여주었다.
싱글턴 INSTANCE 에 접근하거나 사용할때에는 아래처럼 코드를 작성한다.
public class SingletonTest {
public static void main(String[] args) {
System.out.println("1: " + ElvisStaticMember.INSTANCE);
System.out.println("2: " + ElvisStaticMember.INSTANCE);
System.out.println("3: " + ElvisStaticMember.INSTANCE);
}
}
- 정적 팩토리 메소드
두번째 방법은 해당 객체에 접근하는 정적 팩토리 메소드(static 메소드)를 사용하는것이다.
public class ElvisStaticFactoryMethod {
private ElvisStaticFactoryMethod() {}
private static final ElvisStaticFactoryMethod INSTANCE =
new ElvisStaticFactoryMethod();
public static ElvisStaticFactoryMethod getInstance() {
return INSTANCE;
}
}
정적 멤버와 마찬가지의 이유로 외부 class 의 접근을 막기 위해서 생성자를 private 접근제어자로 선언해준다. 그리고 정적 멤버를 public 이 아닌 private 로 선언한다. 이 상태에서는 외부 class 에서 해당 객체에 접근할 수 없는데 정적 팩토리 메소드를 통해 접근할 수 있도록 public static 메소드를 선언한다.
- 정적 멤버와 정적 팩토리 메소드의 문제점
위의 2 가지 싱글턴 구현방법에는 문제점이 있다. 사실 일반적인 Web 개발에서 이 문제점들을 직접적으로 경험할일이 없을 수도 있지만 어쨌든 문제이긴 하므로 소개하고자 한다.
우선 첫째로 클라이언트는 AccessibleObject.setAccessible 메소드로 권한을 획득하고 리플렉션을 통해 private 생성자를 호출할 수 있다.
public class SingletonTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Constructor<?> con = ElvisStaticMember.class.getDeclaredConstructors()[0];
con.setAccessible(true);
ElvisStaticMember newInstance = (ElvisStaticMember) con.newInstance();
System.out.println("origin instance: " + ElvisStaticMember.INSTANCE);
System.out.println("new instance: " + newInstance);
}
}
위의 코드를 실제로 수행해보면 인스턴스 주소가 다르게 나오는것을 확인할 수 있다.
두번째로는 Serializable 에 관한 문제가 있다. 직렬화를 구현하려면 Serializable 인터페이스를 구현하면 된다. 문제는 역직렬화시 싱글톤이 깨진다는것이다.
이를 방지하려면 모든 필드를 transient 로 선언하고 readResolve 메소드를 추가해야 한다.
- enum
enum 은 상수형을 선언할 때 사용되지만 기본적으로 싱글턴이기 때문에 enum 을 이용해서도 구현할 수 있다.
public enum ElvisSingleton {
INSTANCE;
public void someMethod() {
System.out.println("someMethod");
}
}
특별히 접근제어자나 정적 여부 final 로 세세하게 컨트롤 하지 않아도 간결하고 깔끔하게 구현할 수 있다. 단순히 깔끔하기만 할 뿐 아니라 앞에서 언급한 2 가지 문제점도 자동으로 해결된다. 원소가 하나인 enum 자료형은 싱글턴 구현을 위한 최고의 방법이다.
'Language > Java' 카테고리의 다른 글
이펙티브 자바 - 유효기간이 지난 객체 참조 (0) | 2021.09.19 |
---|---|
이펙티브 자바 - 불필요한 객체 생성 (0) | 2021.09.18 |
이펙티브 자바 - Builder 패턴 (0) | 2021.09.16 |
이펙티브 자바 - 정적 팩토리 메소드 (0) | 2021.09.05 |
[Java 8] 날짜 API - 2 (0) | 2021.02.11 |
댓글