자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
정적 유틸리티 클래스와 싱글턴 클래스의 남용 문제
정적 유틸리티 클래스로 구현한 맞춤법 검사기 살펴보기
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // 인스턴스 생성 방지
public static boolean isValid(String word) { ... }
public static String suggestions(String typo) { ... }
}
- 이전에 우리는 정적 유틸리티 클래스를 배웠다.
- 위의 코드는 정적 유틸리티 클래스를 활용한 맞춤법 검사기의 예이다.
싱글턴 클래스로 구현한 맞춤법 검사기 살펴보기
public class SpellChecker {
private static final Lexicon dictionary = ...;
private static INSTANCE = new SpellChecker();
private SpellChecker() {} // 인스턴스 생성 방지
public static boolean isValid(String word) { ... }
public static String suggestions(String typo) { ... }
}
- 이전에 우리는 싱글턴 패턴도 배워봤다.
- 위의 코드는 싱글턴 패턴을 활용한 맞춤법 검사기의 예이다.
문제점 살펴보기
업무의 특징 살펴보기
- 맞춤법 검사라는 프로세스의 특성은 어떤 언어, 어떤 분야의 글을 사용하냐에 따라 달라질 수 있다.
- 즉, 검사에 필요한 사전이 바뀔 수 있다.
- 그런데 위의 정적 유틸리티와 싱글턴 클래스로 구현한 코드에서
dictionary
가 고정되어 있다.
정적 유틸리티 클래스와 싱글턴의 한계
- 정적 유틸리티 클래스와 싱글턴 패턴은 객체지향의 장점을 버리면서 특수한 목적으로 사용했던 코드 패턴이다.
- 이러한 패턴을 남용하면, 객체지향의 장점인 뛰어난 변화 대응 능력은 사라진다.
- 매번 구현 클래스를 찾아가서 해당 구현 클래스의 코드를 바꿔가며 쓰는 것은 좋지 않다.
OCP(Open-Closed Principle)
를 어기는 것이다.
정적 유틸리티 클래스와 싱글턴을 쓰면 안되는 경우
- 사용하는 자원에 따라 동작이 달라지는 클래스
- ex) JDBC와 같은 라이브러리를 만든다고 하면, 내부에 사용할 DB 드라이버를 고정하는 것이 아니라, 외부에서 주입받아야 한다.
의존 객체 주입으로 문제 해결하기
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(Stirng word) { ... }
public List<String> suggestions(String typo) { ... }
}
- 인스턴스를 생성할 때, 필요한 객체를 넘겨줌으로써 여러가지 상황에 유연하게 대응할 수 있다.
- 유연성이 높아지고, 테스트에 더욱 용이한 코드가 된다.
- 불변을 보장하여, 같은 자원을 사용하려는 여러 스레드가 해당 객체를 안심하고 사용할 수 있다.
- 생성자, 정적 팩터리, 빌더에 모두 똑같이 응용할 수 있다.
쓸만한 변형
public class Item5Test {
enum COLOR {
RED, BLUE, GREEN
}
static abstract class Tile {
final COLOR tileColor;
Tile (COLOR color) {
this.tileColor = color;
}
public String tileName() {
return tileColor.name() + " color " + this.getClass().getSimpleName();
}
}
static class CeramicTile extends Tile {
CeramicTile(COLOR color) {
super(color);
}
}
static class StoneTile extends Tile {
StoneTile(COLOR color) {
super(color);
}
}
static class Mosaic {
Mosaic(Tile tile) {
System.out.println("I'm a Mosaic with " + tile.tileName());
}
}
Mosaic create(Supplier<? extends Tile> tileFactory) {
// factory 를 받은 만큼 몇번이고 생성해도 상관없다. 자유롭게 이용할 수 있다.
return new Mosaic(tileFactory.get());
}
@Test
public void test() {
Mosaic result = create(() -> new StoneTile(COLOR.RED));
}
}
- 위 코드에서는
tileFacotry
로 전달받은Supplier
를 이용하여,Mosaic
객체를 만들어 반환할 수 있다.
핵심 정리
- 클래스가 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면, 싱글턴 및 정적 유틸리티 클래스 어울리지 않는다.
- 클래스가 해당 자원을 직접 만들게 하지 말고, 해당 자원이나 팩터리를 주입받아 이용해보자.
- 이 기법은 클래스 유연성, 재사용성, 테스트 용이성을 크게 개선해준다.
반응형
'Java > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바, 쉽게 정리하기 - item7. 다 쓴 객체 참조를 해제하라 (0) | 2021.12.27 |
---|---|
이펙티브 자바, 쉽게 정리하기 - item6. 불필요한 객체 생성을 피하라 (0) | 2021.12.26 |
이펙티브 자바, 쉽게 정리하기 - item4. 인스턴스화를 막으려면 private 생성자를 사용하라 (0) | 2021.12.25 |
이펙티브 자바, 쉽게 정리하기 - item3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2021.12.24 |
이펙티브 자바, 쉽게 정리하기 - item2. 생성자에 매개변수가 많다면, 빌더 패턴을 고려하라 (0) | 2021.12.23 |