[아이템 1] 생성자 대신 정적 팩터리 메서드를 고려하라
장점
public class Bike {
private int wheelCount;
private String color;
private Bike(int wheelCount, String color) {
this.wheelCount = wheelCount;
this.color = color;
}
public static Bike createBike(String color) {
return new Bike(2, color);
}
public static Bike createTricycle(String color) {
return new Bike(3, color);
}
}
public class Bike {
private int wheelCount;
private String color;
private static final Bike redBike = new Bike(2, "red");
private static final Bike betaRobot = new Bike(2, "blue");
public Bike(int wheelCount, String color) {
this.wheelCount = wheelCount;
this.color = color;
}
public static Bike getInstanceRedBike() {
return redBike;
}
public static Bike getInstanceBlueBike() {
return betaRobot;
}
}
- 휠이 2개이고 색이 레드와 블루인 경우 굳이 새로 인스턴스를 만들 필요없다.
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
- 반환 값을 하위 클래스로 설정하면 되기 때문이다.
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 매개변수를 원하는대로 설정할 수 있기 때문이다.
- 정적 팩토리 메소드 작성 시점에는 반환 객체의 클래스가 존재하지 않아도 된다.
단점
- 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.
- 생성자처럼 명확히 드러나지 않는다.
정적 팩터리 메서드의 메소드명을 짓는 일반적인 규약
- from: 매개변수를 받아서 해당 타입의 인스턴스를 반환
- Date date = Date.from(instant);
- of: 여러 매개변수를 받아서 인스턴스 반환
- Set cards = EnumSet.of(JACK, QUEEN, KING);
- valueOf: from과 of의 더 자세한 버전
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance / getInstance: 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.
- StackWalker luke = StackWalker.getInstance(options);
- create / newInstance: 매번 새로운 인스턴스를 생성해 반환한다.
- Object newArray = Array.newInstance(classObject, arrayLen);
- getType: getInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용
- FileStore fs = Files.getFileStore(path);
- newType: newInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용
- BufferedReader br = Files.newBufferedReader(path);
- type: getType과 newType의 간결한 버전
- List litany = Collections.list(someList);
[아이템 3] private 생성자나 열거 타입으로 싱글턴임을 보증하라
private 생성자와 public static 필드
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public void leaveTheBuilding() { }
}
private 생성자와 public static 정적 팩토리 메서드
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() {
return INSTANCE;
}
public void leaveTheBuilding() { }
}
원소가 하나인 Enum 타입 선언
public enum Elvis {
INSTANCE;
public void sleep() { }
}
[아이템 4] 인스턴스화를 막으려거든 private 생성자를 사용하라
- 유틸성 클래스는 보통 인스턴스화해서 쓰기보단, static method 들로 구성하고 많이 쓴다.
- 하지만 java 컴파일러는 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어낸다.
- 따라서 인스턴스화를 막으려면 private 생성자를 명시적으로 생성해주어야 한다.
- 혹시라도 클래스 내부에서 생성자를 호출하지 않도록 오류를 던지는 것도 좋다.
public class BikeUtils {
private BikeUtils() {
throw new AssertionError();
}
// static method (유틸성)
public static <T> T convertObject(..) {...}
}
[아이템 5] 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
- 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글톤 방식이 적합하지 않다.
- 이럴 때는 인스턴스를 생성할 때 생성자, static factory method, builder로 필요한 자원을 넘겨주는 방식이 좋다.
- 이것을 의존객체 주입 패턴 이라 한다.
- 잘못된 예시
public class SpellChecker {
private final Lexicon dictionary = new KoreanDic();
private SpellChecker() {}
public boolean isValid(String word) { }
public List<String> suggestions(String typo) { }
}
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = dictionary;
}
public boolean isValid(String word) { }
public List<String> suggestions(String typo) { }
}
- 올바른 예시2 (Factory를 넘겨주는 방식)
public SpellChecker(Supplier<? extends Lexicon> dicFactory) {
this.dictionary = dicFactory.get();
}
REFERENCES