2022.02.21
정적 팩토리 메서드란 객체 생성을 생성자가 아닌 정적(static) 메서드로 하는 것
을 말한다.
“객체 생성은 생성자가 하는데, 왜 정적 팩토리 메서드를 따로 만들어서 객체 생성을 할까?”
생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 반면, 정적 팩토리 메서드를 사용하면 메서드 이름에 객체 생성 목적을 담아낼 수 있다.
예를들어, 학생 객체를 생성한다고 할 때 생성자 사용하는 경우, 정적 메서드를 사용하는 경우를 비교 해보자.
public class Student {
private String name;
private int idNumber;
public Student(String name) {
this.name = name;
this.idNumber = 99999;
}
public Student(int idNumber) {
this.idNumber = idNumber;
this.name = "대학생";
}
}
Student student1 = new Student("슬로");
Student student2 = new Student(11222334);
Student 객체를 생성자를 오버라이딩 해서 사용하고 있다. 그런데 만약 개발자가 Student 객체 내부 구조에 대해 잘 알고 있지 않다면, 이름이나 학번을 생성자 인자로 보내야하는 사실을 모를 수 있다.
public class Student {
private String name;
private int idNumber;
private Student(String name, int idNumber) {
this.name = name;
this.idNumber = idNumber;
}
public static Student createByName(String name) {
return new Student(name, 999999);
}
public static Student createByIdNumber(int idNumber) {
return new Student("슬로", idNumber);
}
}
Student student1 = Student.createByName("슬로");
Student student2 = Student.createByIdNumber(999999);
createByName
, createByIdNumber
모두 학생 객체를 생성하고 반환하는 정적 메서드이다. 메서드 이름만 봐도 이름으로 생성하는지, 학번으로 생성하는지 단번에 이해할 수 있다.
이처럼 정적 팩토리 메서드를 사용하면 생성의 목적을 이름에 표현할 수 있기 때문에 가독성이 좋아지는 효과
가 있다.
인스턴스를 새로 만들 필요 없이 새로 생성한 인스턴스를 캐싱하여 재활용할 수 있기 때문에 불필요한 객체 생성을 피할 수 있다.
또한 생성자의 접근 제한자를 private
로 설정하여 불필요한 객체 생성을 막을 수 있다.
public class CarFactory {
private CarFactory() {}
private static class CarFactoryHolder {
private static final CarFactory INSTANCE = new CarFactory();
}
public static CarFactory getInstance() {
return CarFactoryHolder.INSTANCE;
}
}
정적 팩터리 메서드를 사용하면, 반환할 객체의 클래스를 자유롭게 선택할 수 있게하는 유연성
을 가질 수 있게 된다.
public static Grade of(int score) {
if (score >= 90) {
return new A();
}
if (score >= 80) {
return new B();
}
// ...
return new F();
}
class A extends Grade {
// ...
}
class B extends Grade {
// ...
}
public class CarDto {
private final String name;
private final int location;
public CarDto(final String name, final int location) {
this.name = name;
this.location = location;
}
public static CarDto from(Car car) {
return new CarDto(car.getName().getName(), car.getLocation());
}
}
// 정적 팩토리 메서드를 사용한 경우
CarDto carDto = CarDto.from(car);
// 생성자를 사용한 경우
// 내부가 이름, 위치 정보로 구현된다는 것을 알 수 있다.
CarDto carDto = new CarDto(car.getName(), car.getLocation());
정적 메소드에서 입력받은 자동차 이름 배열을 그대로 매개변수로 받아서 Car List로 가공한 뒤에 Cars 객체를 생성한다. 이처럼 정적 메소드에서 가공 처리를 한다면, controller나 view에서 각자 맡은 역할만 알맞게 수행할 수 있다.
public class Cars {
private final List<Car> cars;
private Cars(List<Car> cars) {
cars = Collections.unmodifiableList(cars);
validateDuplicate(cars);
this.cars = cars;
}
public static Cars from(String[] names) {
return new Cars(Arrays.stream(names)
.map(Name::new)
.map(Car::new)
.collect(Collectors.toList()));
}
}
두 번째 단점을 보완하기 위해서 널리 알려진 규약을 따라 메서드 이름을 짓는 것이 좋다.
from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
Data d = Date.from(instant);
of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
Set<Rank> faceCars = EnumSet.of(JACK, QUEEN, KING);
valueOf : from과 of의 더 자세한 버전
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance | getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
StackWalker luke = StackWalker.getInstance(options);
create | newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해서 반환함을 보장한다.
Object newArray = Array.newInstance(classObject, arrayLen);
getType : getInstance와 같으나, 생성한 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. Type은 팩터리 메서드가 반환할 객체의 타입이다.
FileStore fs = Files.getFileStore(path);
newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. Type은 팩터리 메서드가 반환할 객체의 타입이다.
BufferedReader br = Files.newBufferedReader(path);
type : getType과 newType의 간결한 버전
List<Complaint> litany = Collections.list(legacyLitany);
정적 팩토리 메서드
생성자
의미를 전달하거나 의도를 쉽게 파악할 수 있다. 생성자를 사용하면, 어떤 필드를 내부적으로 가지는지를 파악할 수 있다. 팩토리 메서드를 사용하면, 생성 목적을 파악하기 쉽고, 전달되는 데이터가 가공 처리를 거칠 수도 있겠다는 추측을 할 수 있다.