Skills/Java

Java - 정적 팩토리 메서드를 고려하는 상황

aoaa 2022. 10. 8. 18:56

 조슈아 블로치의 Effective Java는 1장에 나오는 '생성자대신 정적 팩토리 메서드를 고려하라'는 주제로 시작됩니다. 

생성자대신 사용하라는 것은 두 개 모두 클래스의 인스턴스를 얻는 수단이기 때문이죠. 둘을 비교하면서 살펴보겠습니다.


1. 정적 팩토리 메서드 (Static Factory Method)

 먼저 정적 팩토리 메서드는 해당 클래스의 인스턴스를 반환하는 클래스 메서드입니다. 설명보다는 코드로 한번 보는 것이 좋을 것 같습니다.

// 생성자 방식
public class Constructor{
	String cons;
    
    public cons(String name){
    	this.name=name;
        }
        
    public static void main(String[] arts){
    	Constructor cons = new Constructor("ROME");
        }
}

// 정적 팩토리 메서드
public static Constructor getname(String cons){
	Contructor c = new Constructor();
    c.cons=cons;
	return Constructor;
}

 

1.1 이름을 가질 수 있음

 정적 팩토리 메서드와 생성자 방식으로 인스턴스를 생성한 코드입니다. 생성자의 경우 new Constructor("ROME")처럼 해당 매개변수와 반환 객체의 특성(생성자의 역할)을 제대로 알 수가 없습니다. 반면 정적 팩토리 메서드의 경우는 getname이라는 이름을 만들어 이름을 가져온다라는 객체의 역할을 쉽게 묘사할 수 있습니다.

 하나의 시그니처로는 생성자를 하나밖에 만들 수 없어 생성자를 추가하면 될거라고 생각하지만, 생성자를 추가한 API는 각 생성자가 어떤 것을 의미하는지 기억하기 어렵기 때문에 다른 것을 호출하는 실수가 발생할 수 있습니다. 하지만 이렇게 일므을 가진 정적 팩토리 메서드는 생성자가 여러개 필요한 상황이 온다면 차이를 드러내는 이름을 지어 객체의 역할을 좀 더 잘 나타낼 수 있습니다.

 

1.2 호출 시마다 인스턴스 생성할 필요가 없음

public class StaticFactoryMethod {
    private static final StaticFactoryMethod STATIC_FACTORY_METHOD = new StaticFactoryMethod();
    
    private StaticFactoryMethod() {}
    
    static public StaticFactoryMethod getNewInstanceByNumber() {
        return STATIC_FACTORY_METHOD;
    }
}
-------------------------------------------------------------------------------------------------
public static void main(String args[]){
    StaticFactoryMethod staticFactoryMethod = StaticFactoryMethod.getNewInstance();
    StaticFactoryMethod staticFactoryMethod2 = StaticFactoryMethod.getNewInstance();
    
    System.out.println("staticFactoryMethod == staticFactoryMethod2 :  " +  ( staticFactoryMethod == staticFactoryMethod2 ));
}

 위처럼 static으로 클래스 객체를 미리 생성해놓으면 불필요한 객체 생성을 낭비할 수 있습니다. 

불변 클래스를 생성하고 생성해놓은 인스턴스를 재활용하여 객체 생성을 피할 수 있습니다. 

 

 

1.3 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있음

 책에서는 Java의 Collections을 예로 들었는데 API를 확인 해보겠습니다.

public interface List<E> extends Collection<E> {
    static <E> List<E> of() {
        return (List<E>) ImmutableCollections.ListN.EMPTY_LIST;
    }
}
--------------------------------------------------------------------
public class Collections {
     private Collections() { }

     public static final List EMPTY_LIST = new EmptyList<>();

     @SuppressWarnings("unchecked")
     public static final <T> List<T> emptyList() {
         return (List<T>) EMPTY_LIST;
     }
}

 자바 9에서 등장한 List의 인터페이스인 of()는 인터페이스를 반환하도록 정적 팩토리 메서드로 선언되어 있습니다. 이는 반환 클래스가 무엇인지 알필요 없이 of()라는 메서드의 기능을 알고 사용하면됩니다. (Java 8부터 정적 메서드 사용)

 Java 8 등장 전에는 인터페이스의 유사 클래스를 만들어 그 안에 정적 메서드를 정의하는 방식으로 우회했습니다.

Collections 클래스의 emptyList() 메서드를 보면 접근 지정자를 private를 해놓아 객체 생성을 막아놓았습니다.

 

 이처럼 java.util.Collections 라는 라이브러리는 클래스를 공개하지 않고 API를 작게 만들어 인터페이스대로 동작하는 객체를 얻는 것을 알 수 있기 때문에 실제 구현 클래스를 찾아볼 필요가 없게 됩니다.

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

  1.3과 비슷한데 같은 이름의 메서드여도 매개변수의 개수에 따라 return 받는 클래스를 아무 하위타입의 클래스를 return 받을 수 있습니다.

 

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

JDBC와 같은 서비스 제공자를 보면 이 구현체들을 클라이언트에 제공하는 역할을 프레임워크가 통제하여서 클라이언트를 구현체로부터 분리합니다. 클라이언트가 서비스 접근 API를 사용할 때 원하는 구현체의 조건을 명시 가능하며, 명시하지 않으면 기본 구현체를 반환하거나 지원하는 구현체들을 하나씩 돌아가면서 반환하게 되는데 이를 정적 팩터리의 개념으로 구현합니다. 

 


2. 단점

 2.1 상속 시 public, protected 생성자가 필요하여 정적 팩토리 메서드만 제공 시 하위 클래스를 만들 수 없음

위에서 Collections의 클래스가 private인 것을 확인했었는데 이는 다른 클래스의 부모 클래스가 될 수 없음을 의미합니다.

 2.2 프로그래머가 찾기 어려움

 생성자처럼 API 설명에 명확하게 드러나지 않으면 사용자가 정적 팩토리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 합니다. 이는 API 문서를 잘 작성하여 알려진 규약을 따라 짓는 식으로 완화해야 해줘야 합니다.

'Skills > Java' 카테고리의 다른 글

Java - 커넥션 풀과 데이터소스  (0) 2022.10.13
Java - static 키워드의 사용  (0) 2022.10.11
Java - JDBC  (0) 2022.10.04
Java - equals(), hashcode() 메서드의 사용  (0) 2022.10.01
Java - 바이트 코드  (0) 2022.09.20