본문은 Effective Java를 읽고 정리한 내용을 기반으로 작성된 글입니다.
item 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라
☑️ 싱글턴 : 인스턴스를 오직 하나만 생성할 수 있는 클래스
클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다.
- 싱글턴으로 만들어진 클래스에 의존하는 클라이언트 코드는 테스트가 어렵기 때문
- 의존성 주입을 통해 테스트하도록 설계하는게 바람직함
[싱글턴을 만드는 방법]
☑️ 방법 1) private 생성자 + public static final 필드
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
}
- private 생성자 → 외부에서 호출 불가능
- public static final 필드를 이용해 해당 클래스 타입의 인스턴스를 만든다.
- public이나 protect 생성자가 없음 → Elvis 클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 하나뿐임이 보장된다.
- 정적 변수 ‘INSTANCE’가 클래스의 로딩 시점에 생성되고, 그 이후에는 더이상 Elvis 객체를 생성할 수 없도록 생성자가 private으로 제한되어 있기 때문 → 애플리케이션이 종료되기 전까지 인스턴스는 단 하나만 존재함
[장점]
- 해당 클래스가 싱글턴임이 API에 명백히 드러난다.
- 간결하다
☑️ 방법 2) private 생성자 + 정적 팩터리 메서드
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를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.
public static Elvis getInstance() {
return new Elvis();
}
→ getInstance 메서드 수정을 통해 클라이언트 코드 수정없이 동작을 변경 가능
- 원한다면 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다.(아이템 30)
public class MetaElvis<T> {
private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();
private MetaElvis() { }
@SuppressWarnings("unchecked") // 제네릭 싱글턴 팩토리
public static <E> MetaElvis<E> getInstance() {
return (MetaElvis<E>) INSTANCE;
}
public void say(T t) {
System.out.println(t);
}
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
}
→ 인스턴스는 동일하지만 각각의 타입으로 바꿔서 사용 가능
- 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다. (아이템 43, 44)
☑️ 방법 3) 열거 타입 방식(Enum) - 바람직한 방법
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println();
}
}
- public 필드 방식과 비슷하지만 더 간결하고 직렬화하기 쉽다.
- 대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.
item 4. 인스턴스화를 막으려거든 private 생성자를 사용하라
단순히 정적 메서드와 정적 필드만을 담은 클래스는 객체지향적으로 사고하지 않는 이들이 종종 남용하는 방식이에 그리 곱게 보이지는 않지만, 분명 나름의 쓰임새가 있다.
ex) Math, Arrays 클래스
정적 메서드만을 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 게 아니다.
→ 하지만 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만든다.
→ private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.
public class UtilityClass {
private UtilityClass() {
throw new AssertionError();
}
}
- private 생성자 → 그 안에 에러 throw
- 생성자가 분명 존재하는데 호출할 수 없어 직관적이지 않은 코드 → 주석을 달아두자
- 상속을 불가능하게 하는 효과도 있다.
- 상위 클래스의 생성자에 접근할 길이 막혀버리기 때문
'Book > 이펙티브 자바' 카테고리의 다른 글
[Effective Java] item 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (1) | 2024.01.27 |
---|---|
[Effective Java] item 2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.12.28 |
[Effective Java] item 1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2023.12.28 |