728x90
반응형
아이템 3 private 생성자나 열거 타입으로 싱글턴임을 보증하라
싱글턴(singleton)
- 인스턴스를 오직 하나만 생성할 수 있는 클래스
- 적용 대상: 무상태(stateless) 객체 or 설계상 유일해야 하는 시스템 컴포넌트
- (-) 테스트 어려움
- 인터페이스 기반 설계 X -> Mock 대체 X
싱글턴 구현 방식
1) `public static final` 필드 방식
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
public void leaveTheBuilding() {...}
}
- 클래스 초기화 시점에 인스턴스 한 번 생성
- API에 싱글턴임이 명확히 드러남
- 리플랙션(`AccessibleObject.setAccessible`) 공격 시 2번째 객체 생성 O -> 생성자에서 방어 코드 추가 필요
- 직렬화 시 `readResolve()` 메서드 구현 필요
2) 정적 팩터리 메서드 방식
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {...}
}
- API 변경 X 싱글턴 -> 일반 클래스로 변경 O
- 스레드별 다른 객체 반환하는 등 유연성 ↑
- 메서드 참조(`Elvis::get Instance`) -> `Supplier<Elvis>` 사용 O
- 필요 이상으로 유연성 필요 X -> 1) 방식인 `public static final`이 더 간결
- 직렬화 시 `readResolve()` 필요
// 싱글턴임을 보장해주는 readResolve 메서드
private Object readResolve() {
// '진짜' Elvis 반환, 가짜 Elvis -> 가비지 컬렉터에 맡김
return INSTANCE;
}
3) 원소가 하나인 열거 타입 (Enum)
public Enum Elvis {
INSTANCE;
public void leaveTheBuilding() {...}
}
- 가장 간결하고 직렬화/리플렉션 공격에도 안전
- Enum은 JVM에서 싱글턴 보장
- 다른 클래스 상속할 수 X But, 인터페이스 구현 O
아이템 4 인스턴스화를 막으려거든 private 생성자를 사용하라
필요 이유
- 정적 메서드와 정적 필드만 담은 유틸리티 클래스 만들 때
- ex. `java.lang.Math`, `java.util.Arrays`, `java.util.Collections`
- `final`클래스 관련 메서드 모음에 유용
- `final` 클래스를 상속해서 하위 클래스에 메서드 넣는 것 X
- 인스턴스로 사용 X 설계해도 생성자 명시 X -> 컴파일러) 자동으로 `public` 기본 생성자 생성
=> 의도치 X 인스턴스화 O
잘못된 접근: 추상 클래스 사용
- (-) 인스턴스화 막을 수 있지만, 하위 클래스 통해 인스턴스화 O
- (-) 상속해서 쓰라는 의도로 오해 O
해결 방법
명시적으로 `private` 생성자 선언
public class UtilityClass {
// 기본 생성자가 만들어지는 것을 막음(인스턴스화 방지용)
private UtilityClass() {
throw new AssertionError();
}
...
}
- `private` 생성자 -> 클래스 외부에서 인스턴스 생성 X
- `AssertionError`는 선택 사항 But, 클래스 내부에서 실수로 호출 방지
- 주석으로 "인스턴스화 방지용"임을 명시하는 것이 좋음
+ 상속 방지
모든 생성자는 상위 클래스의 생성자를 호출해야 함
But, 상위 클래스의 생성자가 `private` -> 하위 클래스에서 접근 X
728x90
반응형