많은 클래스가 하나 이상의 자원에 의존한다.(컴포지션)
이 의존하는 클래스를 정적 유틸리티 클래스(아이템 4)로 구현하게 되면 유연하지 않고 테스트 하기 어려운 구조가 된다.
-
정적 유틸리티의 안좋은 예- 유연하지 않고 테스트 어렵다.
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker() { }
public static SpellChecker INSTANCE = new SpellChecker();
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}
- 싱글톤의 안좋은 예- 유연하지 않고 테스트 어렵다.
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker() { }
public static SpellChecker INSTANCE = new SpellChecker();
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}
- SpellChecker가 여러 사전을 사용할 수 있도록 하기 위해서 좋지 않은 방법 - 멀티스레드 환경X
public class SpellChecker {
private Lexicon dictionary = ...;
public static void changeDictionary(){
this.dictionary = ...
}
}
클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준 다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
이 자원들을 클래스가 직접 만들게 해서도 안 된다.
대신 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식을 사용한다.(불변 보장)
이는 의존 객체 주입의 한 형태로, 객체를 생성할 때 의존 객체를 주입해주면 된다. => c.f. Strategy Pattern
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) { // 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨준다
this.dictionary = dictionary;
}
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { }
}
이 패턴의 변형으로, 생성자에 자원 팩터리를 넘겨주는 방식이 있다.(Factory Method Pattern)
팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다.
ex) Supplier<T> interface(java8)
Supplier<T>를 입력으로 받는 메서드는 일반적으로 한정적 와일드카드 타입을 사용해 팩터리의 타입 매개변수를 제한해야 한다.
이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다.
// 클라이언트가 제공한 팩터리가 생성한 타일(Tile)들로 구성된 모자이크 (Mosaic)를 만드는 메서드
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
예를 들어 어떤 사람의 이름과 생일을 입력해두고 getAge()로 나이를 가져오는 Person 클래스를 만든다고 하자.
package com.example.sypark9646.item5;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class Person {
final String name;
private final LocalDate dateOfBirth;
private final LocalDate currentDate;
public Person(String name, LocalDate dateOfBirth) {
this(name, dateOfBirth, LocalDate.now());
}
public Person(String name, LocalDate dateOfBirth, LocalDate currentDate) {
this.name = name;
this.dateOfBirth = dateOfBirth;
this.currentDate = currentDate;
}
long getAge() {
return ChronoUnit.YEARS.between(dateOfBirth, currentDate);
}
public static void printAge(PersonSupplierConstruct person) {
System.out.println(person.name + " is " + person.getAge());
}
}
위 방법의 경우, getAge ()는 현재 날짜가 아닌 Person 객체가 생성 된 시기를 기반으로 한다.
이 문제는 Supplier <LocalDate>를 사용하면 해결된다. 현재 시간을 Supplier를 이용하여 주입하는 것이다.
package com.example.sypark9646.item5;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.function.Supplier;
public class PersonSupplierConstruct {
final String name;
private final LocalDate dateOfBirth;
private final Supplier<LocalDate> currentDate;
public PersonSupplierConstruct(String name, LocalDate dateOfBirth) {
this(name, dateOfBirth, LocalDate::now);
}
public PersonSupplierConstruct(String name, LocalDate dateOfBirth, Supplier<LocalDate> currentDate) {
this.name = name;
this.dateOfBirth = dateOfBirth;
this.currentDate = currentDate;
}
public long getAge() {
return ChronoUnit.YEARS.between(dateOfBirth, currentDate.get());
}
public static void printAge(PersonSupplierConstruct person) {
System.out.println(person.name + " is " + person.getAge());
}
}
용어
- 한정적 와일드카드 타입(bounded wildcard type, 아이템 31)
'책을 읽자 > Effective Java' 카테고리의 다른 글
Item7: 다 쓴 객체 참조를 해제하라 (0) | 2021.02.13 |
---|---|
Item6: 불필요한 객체 생성을 피하라 (0) | 2021.01.30 |
Item4: 인스턴스화를 막으려거든 private 생성자를 사용하라 (1) | 2021.01.25 |
Item3: 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2021.01.23 |
Item2: 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2021.01.17 |