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

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

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

😉 : ‘이펙티브 자바’ 공부 기록입니다.

클래스의 인스턴스를 얻는 가장 대표적인 방법은 public 생성자이다. 하지만 클래스는 생성자와 별도로 클래스의 인스턴스를 반환하는 단순한 정적 메서드인 정적 팩터리 메서드(static factory method)를 제공할 수 있다.

정적 팩터리 메서드의 장점

이름을 가질 수 있다

생성자는 이름을 가질 수 없어서 반환될 객체의 특성을 나타낼 수 없다. 또한 하나의 시그니처로는 하나의 생성자만 만들 수 있다.
하지만 정적 팩터리 메서드는 이름을 가질 수 있어 반환될 객체의 특성을 잘 나타낼 수 있고 시그니처에 제약을 받지 않는다.

호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다

불변 클래스의 경우, 인스턴스를 미리 만들어 놓거나 생성된 인스턴스를 캐싱하여 재활용하는 식으로 매번 인스턴스를 생성하지 않을 수 있다. 이를 통해, 생성 비용이 큰 같은 객체가 자주 요청되는 상황에서 성능 향상을 이룰 수 있다.
플라이웨이트 패턴도 이와 비슷한 기법이다. (추후, 포스트로 설명 추가 예정)
정적 팩터리 메서드를 통해 인스턴스 통제(instance-controlled) 클래스가 될 수 있다.
따라서 필요에 따라 클래스를 싱글톤(Singleton) 또는 인스턴스화 불가(noninstantiable)로 만들 수 있다.

반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다
1
2
3
4
5
6
7
8
9
10
11
public interface HelloService{
    String hello();

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

정적 팩터리 메서드를 통해 반환할 객체의 클래스를 자유롭게 선택할 수 있는 유연성이 생긴다. 따라서, 구현 클래스를 공개하지 않고도 객체를 반환할 수 있어 API를 작게 유지할 수 있다.

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

반환 타입의 하위 타입인 객체이면 모두 반환할 수 있다. 클라이언트는 팩터리가 반환하는 객체가 어느 클래스의 인스턴스인지 알 수도 없고 알 필요도 없다.

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

해당 특징은 서비스 제공자 프레임워크를 만드는 근간이 된다.

서비스 제공자 프레임워크 (Service provider framework)

  • 서비스 인터페이스 (Service Interface)
    구현체의 동작을 정의한다. (단순하게 구현체 interface class라고 생각해도 될 것 같다.)
  • 제공자 등록 API (Provicer registraction API)
    제공자가 구현체를 등록할 때 사용한다. ex) 스프링에서 @Config 를 사용한 Configuration class에서 @Bean을 통해 구현체를 등록하는 것
  • 서비스 접근 API (Service access API)
    클라이언트가 서비스의 인스턴스를 얻을 때 사용한다. ex) 스프링에서 ApplicationContext의 getBean을 통해 서비스의 인스턴스를 얻는 것, @Autowired를 통해 서비스의 인스턴스를 얻는 것
  • 서비스 제공자 인터페이스 (Service provider interface)
    서비스 인터페이스의 인스턴스를 생성하는 팩터리 객체를 설명한다.
  • 서비스 제공자 프레임워크 패턴에는 여러 변형 방식이 있다. -> 브리지 패턴, 의존 객체 주입 등

스프링과 상관없는 예시 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Service Interface
public interface HelloService {
    void sayHello();
}

// 제공자 등록 API
public class HelloServiceProviderRegistry {
    private static Map<String, HelloService> serviceMap = new HashMap<>();

    public static void registerService(String serviceName, HelloService service) {
        serviceMap.put(serviceName, service);
    }

    public static HelloService getService(String serviceName) {
        return serviceMap.get(serviceName);
    }
}

// 서비스 접근 API
public class ServiceAccessor {
    public static void accessService(String serviceName) {
        ServiceInterface service = ServiceProviderRegistry.getService(serviceName);
        if (service != null) {
            service.sayHello();
        } else {
            System.out.println("Service '" + serviceName + "' not found.");
        }
    }
}

// Service Provider Interface
public interface DatabaseConnector {
    Connection connect();
}

// Service Provider Implementation
public class MySQLConnector implements DatabaseConnector {
    @Override
    public Connection connect() {
        // MySQL 연결 코드
        return null;
    }
}

public class PostgreSQLConnector implements DatabaseConnector {
    @Override
    public Connection connect() {
        // PostgreSQL 연결 코드
        return null;
    }
}

정적 팩터리 메서드의 단점

상속이 불가능하다

상속을 하기 위해서는 public/protected 생성자가 필요하다. 따라서 정적 팩터리 메서드만 제공하는 경우에는 상속이 불가능하다. 다만 해당 특징은 상속보다는 컴포지션을 사용하도록 유도하고 불변 타입으로 만들기 위해서는 해당 제약이 강제된다는 점에서 오히려 장점으로 보일 수 있다.
상속이 필요한 경우에는 클래스 위임(class delegation)을 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
//Printer을 상속해야하는 경우
class TextProcessor {
    private Printer printer;

    public TextProcessor(Printer printer) {
        this.printer = printer;
    }

    public void processText(String text) {
        // TODO: 추가적인 작업을 수행
        printer.print(text);
    }
}


정적 팩터리 메서드는 프로그래머가 찾기 힘들다

정적 팩터리 메서드는 생성자와 같이 자바독이 알아서 처리해주지 않아서 API 문서를 잘 써놓아야 한다.



개인적인 느낀점

실제로 나는 개발할 때 정적 팩터리 메서드를 자주 쓰는 편이다. dto를 response, request로 바꾸거나 아니면 반대로 바꾸거나 할 때, 서비스 계층에서 비즈니스 로직만 보이게 하기 위해 to, from 형태의 정적 팩터리 메서드를 자주 쓰는데, 개인적으로는 장점을 많이 체감하고 있다. 위에 나온 것과 같이 ‘이름을 가질 수 있다’가 제일 와닿는 장점이고, 그 다음으로는 ‘반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다’가 다음으로 와닿는 장점이다. 실제로 예시 코드처럼 분기를 쳐서 서로 다른 하위 타입 객체를 리턴해야 하는 종종 있다. (지금 예시는 잘 생각이….)

This post is licensed under CC BY 4.0 by the author.
Contents