생성자는 객체를 생성하기 위해 호출해야 하는 메서드를 의미한다.
생성자 대신 정적 팩터리 메서드를 활용할 수 있다.
그렇다면 정적 팩터리 메서드란 무엇인가?
java.lang 패키지에 Boolean 클래스는 정적 메서드 valueOf() 살펴보자.
public class Boolean {
// ...
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
//...
}
이 메서드는 boolean 값을 인자로 받아 Boolean 객체를 반환한다. 이것이 정적 팩터리 메서드의 예시이다.
정적 팩터리 메서드는 객체를 생성하는 역할을 하는 static 메서드
라고 이해할 수 있다.
정적 팩터리 메서드의 장점을 알아보자.
일반적으로 생성자를 사용한다면 하나의 시그니처로는 하나의 생성자만 만들 수 있다.
Order 클래스가 있다고 가정하자. Order 클래스는 배달과 포장 주문을 구별한다.
public class Order {
private boolean takeout;
private boolean delivery;
private Product product;
}
만약 포장 주문에 대해서는 takeout이 true 값을 갖고, 배달 주문은 delivery가 true 값을 갖도록 하고 싶다면 어떻게 생성자를 만들어야 할까?
생성자를 만들어보자.
public class Order {
private boolean takeout;
private boolean delivery;
private Product product;
public Order(final boolean takeout, final Product product) {
this.takeout = takeout;
this.product = product;
}
//오류가 난다.
public Order(final boolean delivery, final Product product) {
this.delivery = delivery;
this.product = product;
}
}
위와 같이 생성자를 만든다면 컴파일이 되지 않는다. 한 클래스에 동일한 시그니처를 가진 생성자는 두 개 이상 존재할 수 없기 때문이다.
오류를 피하기 위해 입력 매개변수의 순서를 다르게 할 수 있을 것이다. 그러나 이는 좋은 방법이 아닌데, 이 생성자를 호출하는 입장에서 파라미터의 순서에 따라 어떤 객체가 생성되는지 알기 어렵기 때문이다.
이런 경우 정적 팩터리 메서드를 활용할 수 있다.
정적 팩터리 메서드는 이름을 가질 수 있으므로, 반환될 객체의 특성을 쉽게 묘사할 수 있다.
또한 서로 다른 이름을 짓는다면, 시그니처가 같은 여러 개의 생성자가 있는 효과를 누릴 수 있다.
public class Order {
private boolean delivery;
private boolean takeout;
private Product product;
public static Order takeoutOrder(Product product) {
Order order = new Order();
order.takeout = true;
order.product = product;
return order;
}
public static Order deliveryOrder(Product product) {
Order order = new Order();
order.delivery = true;
order.product = product;
return order;
}
}
takeoutOrder() 메서드를 호출하면 포장 Order 객체를 생성할 수 있고, deliveryOrder() 메서드를 호출하면 배달 Order 객체를 생성할 수 있게 된다.
public class Boolean {
// ...
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
//...
}
Boolean 클래스의 정적 팩터리 메서드인 valueOf 메서드는 객체를 직접 생성하지 않고 미리 생성해둔 객체를 반환한다.
이러한 방법으로 Boolean 객체를 TRUE, FALSE 두 개만을 인스턴스로 갖는 불변 객체로 사용할 수 있다.
불변 객체
의 장점을 간단히 서술하자면 다음과 같다.
꼭 불변이 아니더라도, 자주 사용하면서 변하지 않을 객체를 미리 생성해두고 반환하는 방법을 사용할 수도 있다.
아래는 우아한테크코스 사다리 미션에서 사용된 코드 중 일부이다.
정적 팩터리 메서드 from은 사용자 입력을 받아 UserRequestedParticipants 객체를 반환한다.
public class UserRequestedParticipants {
public static final String ALL_PARTICIPANTS_COMMAND = "all";
private static final UserRequestedParticipants ALL_PARTICIPANTS = new UserRequestedParticipants(ALL_PARTICIPANTS_COMMAND);
private final String requestContent;
private UserRequestedParticipants(final String requestContent) {
this.requestContent = requestContent;
}
public static UserRequestedParticipants from(final String requestContent) { //정적 팩터리 메서드
if (requestContent.equals(ALL_PARTICIPANTS_COMMAND)) {
return ALL_PARTICIPANTS;
}
return new UserRequestedParticipants(requestContent);
}
public boolean isAllParticipants() {
return this.equals(ALL_PARTICIPANTS);
}
public String getRequestContent() {
return requestContent;
}
}
이때 사용자의 입력이 "all"과 같다면 미리 생성해둔 ALL_PARTICIPANTS
를 반환한다.
이러한 방법으로 인해 all을 상태로 갖는 모든 UserRequestedParticipants 객체는 오직 하나 뿐임이 보장되며 매번 새로운 객체를 생성할 필요가 없어진다.
위와 같이 정적 팩터리 메서드를 통해 인스턴스를 통제할 수 있다.
인스턴스 통제와 관련한 키워드와 자세한 내용은 다른 아이템에 자세히 나와있으니 더 궁금한 내용이 있다면 참고하자.
public interface HelloService {
void hello();
static HelloService of(String language) {
if (language.equals("ko")) {
return new KoreanHelloService();
}
return new EnglishHelloService();
}
}
public class KoreanHelloService implements HelloService{
@Override
public void hello() {
System.out.println("안녕");
}
}
public class EnglishHelloService implements HelloService{
@Override
public void hello() {
System.out.println("hello");
}
}
HelloService 인터페이스 안에 정적 팩터리 메서드가 존재한다. 이 메서드는 매개변수에 따라 서로 다른 하위 구현체를 반환한다.
public class Main {
public static void main(String[] args) {
HelloService helloServiceFromInterface = HelloService.of("ko");
helloServiceFromInterface.hello(); //안녕하세요
}
}
HelloService 를 사용하는 클라이언트 측에서는 입력하는 매개변수 값에 따라 원하는 하위 타입 객체를 생성 받을 수 있다.
실제로 클라이언트는 어떤 구현체를 사용하는지 알지 못하게 되고 알 필요도 없게 된다. 오직 전달할 매개변수만 신경쓰면 된다.
이는 코드에 굉장한 유연함을 가져다줄 수 있다.
하지만 정적 팩터리 메서드는 다음과 같은 단점을 갖는다.
정적 팩터리 메서드를 사용할 때, 클라이언트가 코드 작성자의 의도대로 객체를 생성하도록 하기 위해 생성자를 private 등으로 막아두는 것이 보통이다.
다만, 이러한 방법은 상속을 불가하게 만든다. 상속을 염두에 두고 있다면 정적 팩터리 메서드를 주의해서 사용해야 한다.
클라이언트가 객체를 생성할 때, new 키워드로 생성자를 호출하는 것은 지극히 자연스럽다.
그러나 생성자 없이, 정적 팩터리 메서드만 존재한다면 클라이언트가 이 메서드를 직접 찾아내야만 객체를 생성할 수 있다.
따라서 클라이언트가 정적 팩터리 메서드를 쉽게 찾을 수 있도록 안내할 방법을 마련해야 한다.
정적 팩터리 메서드는 다음과 같은 네이밍 컨벤션을 갖는다.