1. 개요

Java 생성자는 완전히 초기화된 클래스 인스턴스를 가져오기 위한 기본 메커니즘입니다. 결국 수동 또는 자동으로 의존성을 주입하는 데 필요한 모든 인프라를 제공합니다.

그럼에도 불구하고 몇 가지 특정 사용 사례에서는 동일한 결과를 얻기 위해 정적 팩토리 메서드에 의존하는 것이 좋습니다.

이 예제에서는 정적 팩토리 메소드와 기존 Java 생성자를 사용할 때의 장단점을 강조할 것 입니다.

2. 생성자에 대한 정적 팩토리 메소드의 장점

Java와 같은 객체 지향 언어에서 생성자가 잘못될 수 있는 것은 무엇입니까? 전반적으로 아무것도. 그럼에도 불구하고 유명한 Joshua Block의 Effective Java Item 1 은 다음과 같이 명확하게 명시하고 있습니다.

"생성자 대신 정적 팩토리 메서드를 고려하십시오"

이것이 만병통치약은 아니지만 이 접근 방식을 유지하는 가장 강력한 이유는 다음과 같습니다.

  1. 생성자에는 의미 있는 이름 이 없으므로 항상 언어에서 부과하는 표준 명명 규칙으로 제한됩니다. 정적 팩토리 메소드는 의미 있는 이름을 가질 수 있으므로 그들이 하는 일을 명시적으로 전달합니다.
  2. 정적 팩토리 메서드는 메서드, 하위 유형 및 기본 형식을 구현하는 동일한 유형을 반환할 수 있으므로 보다 유연한 반환 유형 범위를 제공합니다.
  3. 정적 팩토리 메서드는 완전히 초기화된 인스턴스를 미리 구성하는 데 필요한 모든 논리를 캡슐화 할 수 있으므로 생성자에서 이 추가 논리를 이동하는 데 사용할 수 있습니다. 이것은 생성자가 필드를 초기화하는 것 이외의 추가 작업수행하는 것을 방지 합니다.
  4. 정적 팩토리 메소드는 제어된 인스턴스 메소드수 있으며 싱글톤 패턴 이 이 기능의 가장 눈에 띄는 예입니다.

3. JDK의 정적 팩토리 메소드

JDK에는 위에서 설명한 많은 장점을 보여주는 정적 팩토리 메소드의 예가 많이 있습니다. 그 중 일부를 살펴보겠습니다.

3.1. 문자열 클래스

잘 알려진 String 인턴 때문에 String 클래스 생성자를 사용하여 새 String 객체 를 생성 할 가능성은 거의 없습니다 . 그럼에도 불구하고 이것은 완벽하게 합법적입니다.

String value = new String("Baeldung");

이 경우 생성자는 예상되는 동작인 String 객체를 생성합니다 .

또는 정적 팩토리 메서드를 사용하여 String 객체생성 하려는 경우 valueOf() 메서드 의 다음 구현 중 일부를 사용할 수 있습니다 .

String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');

valueOf() 의 여러 오버로드된 구현이 있습니다. 각각 은 메서드에 전달된 인수의 유형(예: int , long , boolean , char 등)에 따라 새로운 String 객체를 반환합니다 .

이름은 메서드가 수행하는 작업을 매우 명확하게 표현합니다. 또한 정적 팩토리 메서드의 이름을 지정하기 위해 Java 에코시스템에서 잘 정립된 표준을 고수합니다.

3.2. 옵션 클래스

JDK에서 정적 팩토리 메소드의 또 다른 깔끔한 예는 Optional 클래스입니다. 이 클래스  empty() , of()ofNullable() 을 포함하여 꽤 의미 있는 이름을 가진 몇 가지 팩토리 메서드를 구현합니다 .

Optional<String> value1 = Optional.empty();
Optional<String> value2 = Optional.of("Baeldung");
Optional<String> value3 = Optional.ofNullable(null);

3.3. 컬렉션 클래스

아마도 JDK에서 정적 팩토리 메소드의 가장 대표적인 예는 Collection 클래스일 것입니다.  이것은 정적 메서드만 구현하는 인스턴스화할 수 없는 클래스입니다.

이들 중 다수는 제공된 컬렉션에 어떤 유형의 알고리즘을 적용한 후 컬렉션도 반환하는 팩토리 메서드입니다.

다음은 클래스의 팩토리 메서드에 대한 몇 가지 일반적인 예입니다.

Collection syncedCollection = Collections.synchronizedCollection(originalCollection);
Set syncedSet = Collections.synchronizedSet(new HashSet());
List<Integer> unmodifiableList = Collections.unmodifiableList(originalList);
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(originalMap);

JDK의 정적 팩토리 메소드의 수는 정말 광범위하므로 간결함을 위해 예제 List을 짧게 유지하겠습니다.

그럼에도 불구하고 위의 예는 Java에서 정적 팩토리 메소드가 어디에나 있는지에 대한 명확한 아이디어를 제공해야 합니다.

4. 커스텀 스태틱 팩토리 메소드

물론 우리는 우리 고유의 정적 팩토리 메소드를 구현할 수 있습니다.  그러나 일반 생성자를 통해 클래스 인스턴스를 만드는 대신 그렇게 할 가치가 있는 때는 언제입니까?

간단한 예를 보겠습니다.

이 순진한 User 클래스를 고려해보자 :

public class User {
    
    private final String name;
    private final String email;
    private final String country;
    
    public User(String name, String email, String country) {
        this.name = name;
        this.email = email;
        this.country = country;
    }
    
    // standard getters / toString
}

이 경우 정적 팩토리 메서드가 표준 생성자보다 나을 수 있음을 나타내는 시각적 경고가 없습니다.

모든 사용자 인스턴스가 국가 필드에 대한 기본값을 갖도록 하려면 어떻게 해야 합니까?

필드를 기본값으로 초기화하면 생성자도 리팩터링해야 하므로 디자인이 더 단단해집니다.

대신 정적 팩토리 메서드를 사용할 수 있습니다.

public static User createWithDefaultCountry(String name, String email) {
    return new User(name, email, "Argentina");
}

국가 필드에 기본값이 할당된 사용자 인스턴스를 얻는 방법은 다음과 같습니다 .

User user = User.createWithDefaultCountry("John", "john@domain.com");

5. 생성자에서 논리 이동

우리의 사용자 우리는 생성자 (경종이 시간에 떨어져 소리가되어야한다)에 더 로직을 추가 할 필요 기능을 구현하기로 결정한 경우 클래스는 신속하게 결함이 설계에 썩어 수 있습니다.

모든 User 개체가 생성 되는 시간을 기록하는 기능을 클래스에 제공하려고 한다고 가정해 보겠습니다 .

이 논리를 생성자에 넣으면 단일 책임 원칙을 위반하게 됩니다. 우리는 필드를 초기화하는 것보다 훨씬 더 많은 일을 하는 모놀리식 생성자로 끝날 것입니다.

정적 팩토리 메서드를 사용하여 디자인을 깨끗하게 유지할 수 있습니다.

public class User {
    
    private static final Logger LOGGER = Logger.getLogger(User.class.getName());
    private final String name;
    private final String email;
    private final String country;
    
    // standard constructors / getters
    
    public static User createWithLoggedInstantiationTime(
      String name, String email, String country) {
        LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
        return new User(name, email, country);
    }
}

개선된 사용자 인스턴스를 만드는 방법은 다음과 같습니다 .

User user 
  = User.createWithLoggedInstantiationTime("John", "john@domain.com", "Argentina");

6. 인스턴스 제어 인스턴스화

위에 표시된 것처럼 완전히 초기화된 사용자 개체를 반환하기 전에 논리 청크를 정적 팩토리 메서드로 캡슐화할 수 있습니다 . 그리고 우리는 관련 없는 여러 작업을 수행해야 하는 책임으로 생성자를 오염시키지 않고 이 작업을 수행할 수 있습니다.

예를 들어 User 클래스를 Singleton 으로 만들고 싶다고 가정 합니다. 인스턴스 제어 정적 팩토리 메서드를 구현하여 이를 달성할 수 있습니다.

public class User {
    
    private static volatile User instance = null;
    
    // other fields / standard constructors / getters
    
    public static User getSingletonInstance(String name, String email, String country) {
        if (instance == null) {
            synchronized (User.class) {
                if (instance == null) {
                    instance = new User(name, email, country);
                }
            }
        }
        return instance;
    }
}

getSingletonInstance() 메서드 의 구현은 동기화된 블록으로 인해 약간의 성능 저하와 함께 스레드로부터 안전 합니다.

이 경우 인스턴스 제어 정적 팩토리 메소드의 구현을 시연하기 위해 지연 초기화를 사용했습니다.

그러나 Singleton을 구현하는 가장 좋은 방법은 직렬화 안전하고 스레드 안전하기 때문에 Java 열거형 유형을 사용하는 것이라는 점을 언급할 가치가 있습니다 . 다양한 접근 방식을 사용하여 싱글톤을 구현하는 방법에 대한 자세한 내용은 이 문서 를 확인 하세요 .

예상대로 이 방법 으로 User 개체를 가져오는 것은 이전 예와 매우 유사합니다.

User user = User.getSingletonInstance("John", "john@domain.com", "Argentina");

7. 결론

이 기사에서는 정적 팩토리 메소드가 일반 Java 생성자를 사용하는 것보다 더 나은 대안이 될 수 있는 몇 가지 사용 사례를 살펴보았습니다.

게다가 이 리팩토링 패턴은 일반적인 워크플로에 매우 밀접하게 뿌리를 두고 있어 대부분의 IDE에서 이를 수행합니다.

물론 Apache NetBeans , IntelliJ IDEAEclipse 는 약간 다른 방식으로 리팩토링을 수행하므로 먼저 IDE 문서를 확인하십시오.

다른 많은 리팩토링 패턴과 마찬가지로 정적 팩토리 메서드를 신중하게 사용해야 하며 더 유연하고 깔끔한 디자인을 생성하는 것과 추가 메서드를 구현해야 하는 비용 사이에서 절충할 가치가 있는 경우에만 사용해야 합니다.

평소와 같이 이 문서에 표시된 모든 코드 샘플 은 GitHub에서 사용할 수 있습니다 .

Junit footer banner