본문 바로가기
java

1. 생성자 대신 정적 팩터리 메서드를 고려하라

by 오우지 2023. 2. 19.

박싱 클래스: primitive 자료형을 wrapper 클래스의 객체로 만드는 과정을 의미, 언박싱은 그 반대를 의미한다.

 

박싱 클래스의 Boolean에는 primitive한 값을 wrapper 클래스로 바꿔주는 valueOf라는 정적 팩터리 메서드가 존재한다.

public static Boolean valueOf(boolean b) {
	return b ? Boolean.TRUE : Boolean.FALSE;
}

 

장점 1. 생성자가 이름을 가질 수 있다.

 

위의 정적 팩토리 메서드처럼 클라이언트는 생성자 말고도 정적 팩터리 메서드를 제공해 클래스를 생성할 수 있다.

 

public class Order {
    private boolean prime;
    
    private boolean urgent;
    
    private Product product;
    
    public static Order primeOrder(Product product) {
        Order order = new Order();
        order.prime = true;
        order.product = product;
        return order;
    }
    
    public static Order urgentOrder(Product product) {
        Order order = new Order();
        order.urgent = true;
        order.product = product;
        return order;
    }
}

기본 생성자는 이름을 변경할 수 없기 때문에 여러 개의 boolean을 element로 가지는 클래스 생성자를 만들 때 사용자 입장에서는 헷갈릴 수 밖에 없다. 이 때 정적 팩터리 메서드를 이용하면 명시적으로 사용자에게 알려줄 수 있다.

 

장점 2. 호출될 때마다 인스턴스를 생성하지 않아도 된다.

 

클래스 인스턴스의 생성을 조절하고 싶을 수 있다. 그럴 때 생성자가 public으로 선언되어 있다면 사용자가 만드는 인스턴스의 갯수를 조절할 수 없다.

public class Settings {

    private boolean useAutoSteering;

    private boolean useABS;

    private Difficulty difficulty;

    public static void main(String[] args) {
        System.out.println(new Settings());
        System.out.println(new Settings());
        System.out.println(new Settings());
    }
}

해당 클래스는 toString을 Override하지 않았다면 각각 다른 주소가 찍힌다.

 

public class Settings {

    private boolean useAutoSteering;

    private boolean useABS;

    private Difficulty difficulty;

    private Settings() {}

    private static final Settings SETTINGS = new Settings();

    private static Settings newInstance() {
        return SETTINGS;
    }
}

하지만 다음과 같이 Settings의 생성자를 private으로 만들어놓고 클래스 내부에서 newInstance()를 통해서만 미리 만들어 놓은 인스턴스를 반환한다면 인스턴스의 생성을 조절할 수 있다.

 

장점 3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

 

인터페이스를 반환 타입으로 설정하고 그 하위 타입을 반환하는 방법의 응용이 가능하다. 이 방법은 구현 클래스를 공개하지 않고도 객체를 반환할 수 있어 API를 작게 유지할 수 있다.

public interface HelloService {

    String hello();

    static HelloService of(String lang) {
        if (lang.equals("ko")) {
            return new KoreanHelloService();
        } else {
            return new EnglishHelloService();
        }
    }
}

 

장점 4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

EnumSet클래스는 public 생성자 없이 정적 팩터리만 제공하는데 원소의 수에 따라 두 하위 클래스 중 하나의 인스턴스를 반환한다.

 

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);
}

 

장점 5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

public static void main(String[] args) {
    ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
    Optional<HelloService> helloServiceOptional = loader.findFirst();
    helloServiceOptional.ifPresent(h -> {
        System.out.println(h.hello());
    });
}

해당 서비스 로더는 어떤 DB를 사용할 지 모르는 JDBC 드라이버를 설계할 때 각 벤더에서 구현한 구체 클래스를 의존성에 추가만 해서 사용할 수 있게 만들어 줄 수 있다.

 

 

단점 1. 상속을 하려면 public이나 protected 생성자가 필요하지만 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

 

위의 구현들에서 보면 생성자는 모두 private으로 막아두고 정적 팩터리 메서드만 제공했는데 그럼 상속이 불가능해진다. 이는 상속보다 컴포지션을 사용하고 불변타입으로 만들려면 이 제약을 지켜야 한다는 점에서 장점이 될 수 있다.

 

 

단점 2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

javadoc을 만들면 주석을 톻애 API 명세에 대한 문서를 만들어주는데 생성자는 별도의 컬럼으로 만들어진다. 하지만 정적 팩터리 메서드는 메서드 항목에 들어가 찾기 힘들어진다.

'java' 카테고리의 다른 글

함수형 프로그래밍  (0) 2021.12.15
또다시 Optional  (0) 2021.12.01