@Test
public void isZeroWhenStarted() {
Location location = new Location();
...
}
이펙티브 자바 아이템 1를 참고해보면 좋을 거 같아요 :)
정적 팩터리 메서드는 객체 생성 역할을 하는 클래스 메서드다.
생성자 자체는 생성되는 객체의 특성을 직관적으로 설명하지는 않는다.
public static Position createStartPosition() {
return new Position(START_POSITION_VALUE);
}
Position position = Position.createStartPosition();
이렇게 메서드 명을 지음으로서 어떤 객체가 생성되는지 더 구체적으로 알 수 있다.
또한 생성자는 똑같은 타입을 파라미터로 받는 생성자 두 개를 만들 수 없는데 이때도 정적 팩토리 메서드로 가능하다.
public class Person {
String name;
String address;
public Person(String name) {
this.name = name;
}
public Person(String address) {
this.address = address;
}
...
}
위는 불가능하다.
public class Person {
String name;
String address;
public Person() {
}
public Person(String name) {
this.name = name;
}
public static Person withName(String name) {
return new Person(name);
}
public static Person withAddress(String address) {
Person person = new Person();
person.address = address;
return person;
}
...
}
public class LottoNumber implements Comparable<LottoNumber> {
public static final int MIN_LOTTO_NUMBER = 1;
public static final int MAX_LOTTO_NUMBER = 45;
private final int number;
public LottoNumber(final int number) {
checkLottoNumberRange(number);
this.number = number;
}
...
}
로또 게임에서 같은 입력 값에 대해 계속 새 인스턴스를 생성할 필요는 없을 것이다.
public static final int MIN_LOTTO_NUMBER = 1;
public static final int MAX_LOTTO_NUMBER = 45;
public static final LottoNumber[] LOTTO_NUMBER_CACHE = new LottoNumber[MAX_LOTTO_NUMBER + 1];
static {
IntStream.rangeClosed(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
.forEach(number -> LOTTO_NUMBER_CACHE[number] = new LottoNumber(number));
}
private final int number;
public static LottoNumber valueOf(final int number) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR]");
}
return LOTTO_NUMBER_CACHE[number];
}
인스턴스를 미리 만들어 놓거나 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
생성 비용이 큰 객체일 때 더 좋다.
이렇게 인스턴스가 언제 살아있게 할지 통제하면 장점은?
코드 출처
우테코 4기 후니 블로그
리턴 타입은 인터페이스로 지정해서 구현체는 노출시키지 않을 수 있다.
public interface Car {
...
}
public class ElectricCar {
int position = 0;
public static Car create(int position) {
return new ElectricCar(position);
}
....
}
API를 만들 떄 이 유연성을 응용하면 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다.
예를 들어 java.util.Collection
에서 정적 팩터리 메서드를 통해 45개의 유틸리티 구현체를 제공한다.
인터페이스만 노출되므로 '개념적인 무게 줄이기' 가능. 프로그래머가 알아야하는 개수와 난이도가 줄어든다.
자바 8부터는
public static
메서드를 인터페이스 추가 가능하다. 그래서 굳이Collecions
라는 클래스를 만들지 않고도 인터페이스에 구현 가능하다.
하지만private static
은 자바 9부터 가능하다. 자바9를 쓰면private static
까지 인터페이스에 추가할 수 있으니java.util.Collection
같은 유틸성 클래스들은 핊요 없어진다.
장점 3의 연장선이다. 유연성에 관해 이야기하고 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
public class Level {
...
public static Level of(int score) {
if (score < 50) {
return new Basic();
} else if (score < 80) {
return new Intermediate();
} else {
return new Advanced();
}
}
...
}
위의 코드에서도 매개변수에 따라 다른 클래스 객체를 반환할 수 있다.
EnumSet
클래스 (아이템36)가 생성자 없이 public static 메서드로 allOf()
, of()
등을 제공하는데, 리턴하는 객체의 타입이 enum 타입의 개수에 따라 RegularEnumSet
또는 JumboEnumSet
으로 달라진다.
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
장점 3, 4와 비슷한 개념이다.
인터페이스나 클래스가 만들어지는 시점에서 하위 타입의 클래스가 존재하지 않아도 나중에 만들 클래스가 기존의 인터페이스나 클래스를 상속 받으면 언제든지 의존성을 주입 받아서 사용가능하다. 반환값이 인터페이스가 되며 정적 팩터리 메서드이 변경없이 구현체를 바꿔 끼울 수 있다.
다른 누군가가 구현하고 있는 코드
public class ElectricCar extends Car {
...
}
public class Car {
public static Car getCar(int position) {
Car car = new Car();
// Car 구현체의 FQCN(Full Qualified Class Name)을 읽어온다
// FQCN에 해당하는 인스턴스를 생성한다.
// car 변수를 해당 인스턴스를 가리키도록 수정
return car;
}
}
getCar
는 실행시점에 위 코드에 뭐가 적혀있냐에 따라 다르게 작동한다.
JDBC
에서 getConnection()
을 쓸 때 실제로 return되어서 나오는 객체는 드라이버마다 다르다 (MySQL 드라이버, Oracle 드라이버..).
서비스 프로바이더
프레임워크는..
DriverManager.registerDriver()
DriverManager.getConnection()
Driver
하지만 사실 JDBC는 서비스 프로바이더
프레임워크를 사용하지 않았다. 서비스 프로바이더 프레임워크는 자바6부터 가능했고, JDBC는 이전에 나왔다.
그래서 java.util.Collections
는 상속할 수 없다.
상속보다 컴포지션을 사용(아이템18) 도록 유도하고 불변 타입(아이템17)로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점이 될 수도 있기는 하다.
생성자는 Javadoc이 자동으로 상단에 모아서 보여준다. 정적 팩토리 메서드는 그렇지 않다.