728x90
반응형
# 다중 인스턴스 방지
인스턴스화를 막기 위해서 private 생성자 사용하자
정적 메서드와 정적 필드만을 가진 클래스
- 기본 타입 , 배열 관련 메서드만 존재하는 클래스
- ex) java.lang.Math , java.util.Arrays
- 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(팩토리)를 모아놓은 클래스
- ex) java.util.Collections
- final 클래스와 관련한 메서드를 모아놓은 클래스
- final 클래스를 상속해서 하위 클래스에 메서드를 넣는 것은 불가능 하기 때문
위와 같이, 정적 멤버만 담은 utility 클래스는 인스턴스로 만들려고 설계한 것이 아님
→ 따라서 외부에서 해당 클래스의 생성자에 접근하지 못하게 해야함
하지만 생성자를 명시하지 않으면 자동으로 컴파일러는 default 생성자를 만들어준다.
public Hello(){}
- Q. 그렇다면 추상 클래스로 만들면 되지 않을까 ?
- 물론 개념적으로 추상클래스는 인스턴스로 만들 수 없다.
- 하지만 해당 추상 클래스를 상속받아 하위 클래스를 만들어 인스턴스화하면 장땡
- 오히려 사용자에게 상속해서 쓰라는 메시지를 잘못 전달하는 경우가 발생할 수 있다.
해결 방법
private 생성자를 통해 클래스의 인스턴스 화를 막을 수 있다.
- 명시적 생성자가 Private으로 설정되어있기 때문에 클래스 외부에서는 해당 생성자에 접근할 수 없다.
- 해당 방식을 통해 상속이 불가능하게 할 수 있다.
// 해당 클래스의 생성자의 인스턴스화를 막는다.
private NoInstance(){
// 주석을 달아 다른 이가 이해할 수 있도록 도움.
throw new AssertionError();
}
# 의존성 주입 [ Dependency Injection ]
자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
클래스들이 자원(혹은 다른 클래스)에 의존하는 경우가 존재한다.
책에 나온 dictionary를 사용하는 오탈자확인(SpellChecker) 예제를 통해 해당 사례를 확인해보자
# 정적 유틸리티 클래스 사용
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} //인스턴스화 방지 (아이템 4)
public static boolean isVaild(String word) { //해당 단어가 유효한가
...
}
public static List<String> suggestions(String typo){ // 단어 제시
...
}
}
// 클래스 외부에서 사용
SpellChecker.isValid(word);
# 싱글턴 방식 사용
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker() {} //인스턴스화 방지 (아이템 4)
public static SpellChecker INSTANCE = new SpellChecker(...);
public static boolean isVaild(String word) { //해당 단어가 유효한가
...
}
public static List<String> suggestions(String typo){ // 단어 제시
...
}
}
// 클래스 외부에서 사용
SpellChecker.INSTANCE.isValid(word);
앞서 소개한 두 방식은 사전(dictionary)가 달라질 수 있기 때문에 확장에 유연하지 않고 테스트 또한 어렵다.
Q. 그러면 SpellChecker가 여러 사전을 사용할 수 있도록 final 키워드를 없애면 교체할 수 있지 않나요?
A. 가능은 하지만 해당 방법으로 해결해도 오류를 발생시키기 쉬우며, 멀티스레드 환경에서는 사용할 수 없는 문제가 있다.
결론 : 사용하는 자원에 따라 동작이 달라지는 클래스는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.
# 의존성(객체) 주입 형태
- 인스턴스 생성시, 생성자에 필요한 자원을 넘겨주는 방식
public class SpellChecker {
private static final Lexicon dictionary = ...;
public SpellChecker(Lexicon dict) { // DI(의존성 주입)
this.dictionary = Obejcts.requiredNonNull(dict);
}
public static boolean isVaild(String word) { //해당 단어가 유효한가
...
}
public static List<String> suggestions(String typo){ // 단어 제시
...
}
}
interface Lexicon {}
//Lexicon을 상속받은 새로운 사용자 사전 구현
public class UserDictionary implements Lexicon {
...
}
// 클래스 외부에서 사용
Lexicon dic = new UserDictionary();
SpellChecker spellChecker = new SpellChecker(dic);
spellChecker.isValid(word);
- 해당 패턴의 응용 방법을 통해 생성자에 자원 팩토리를 넘겨주는 방식 존재
- 팩토리 메소드 패턴
- ex. Supplier<T> 인터페이스 - 해당 방식을 통해 Client는 자신이 명시한 타입의 하위 타입도 생성할 수 있는 팩토리 주입 가능
의존성 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성의 수가 증가하게 되면 코드가 장황해질 수 있다.
Spring에서는 IOC 를 통해 이러한 문제를 해결해준다.
728x90
반응형
'책 > Effective Java' 카테고리의 다른 글
다 쓴 객체 참조 해제하기 (0) | 2022.01.10 |
---|---|
불필요한 객체 생성 피하는 방법 (0) | 2022.01.09 |
Singleton 보장 방법 (0) | 2022.01.03 |
Builder 패턴 (0) | 2022.01.03 |
정적 팩토리 메서드 [ static factory method ] (0) | 2022.01.01 |