본문 바로가기

STUDY/이펙티브자바

2-1) 생성자 대신 정적 팩토리 메소드를 고려하라.

생성자(new) 대신 정적 팩토리 메소드 (Static Factory Method)

 

생성자(new)는 JAVA의 가장 기본적인 문법이고 클래스의 인스턴스를 만들 때 일반적으로 생성자를 사용한다. 하지만 개발자는 클래스에 별도의 정적 팩토리 메소드 (Static Factory Method)를 제공할 수 있다. 클래스의 인스턴스를 반환하는 정적 팩토리 메소드를 제공하면 생성자 대신 사용할 수 있다.

 

public class MyClass {
	// 생성자를 통한 인스턴스 생성
	public MyClass() {}
	
	// 정적 팩토리 메소드를 통한 인스턴스 생성
	public static MyClass getInstance() {
		return new MyClass();
	}
}

간단한 예제 코드를 보면, MyClass는 생성자를 통해서 인스턴스를 만들 수도 있고 getInstance라는 정적 팩토리 메소드를 통해서도 인스턴스를 만들 수 있다.

 

 


장점 1. 이름을 가질 수 있다. (용도를 명확히 표현할 수 있다.)

 

생성자의 이름은 클래스의 이름을 그대로 사용한다. 파라미터를 다르게하여 여러개의 생성자를 만들 수 있지만, 각각의 생성자가 어떤 용도로 존재하는지는 명확히 알 수 없다. 하지만 정적 팩토리 메소드는 메소드의 이름을 자유롭게 지을 수 있어서 그 용도를 명확히 표현할 수 있다.

 

// BigInteger 클래스의 probablePrime 메소드
// 메소드의 이름으로 PrimeNumber, 즉 소수를 반환한다는 사실을 알 수 있다.
public static BigInteger probablePrime(int bitLength, Random rnd)

// probablePrime를 생성자로 만든다고 했을 때, 소수를 만든다는 사실을 쉽게 알 수 없다.
public BigInteger(int bitLength, Random rnd)

 

 


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

 

정적 팩토리 메소드를 사용하면 인스턴스를 미리 만들어 두거나 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다. 클래스의 생성자를 사용할 수 없도록 만들고 정적 팩토리 메소드를 통해서만 인스턴스를 만들 수 있도록 하면, 프로그램 전체에서 사용되는 클래스의 인스턴스를 통제할 수 있다. 이를 통해 클래스를 싱클톤으로 만들 수도있다.

 

// Boolean 클래스는 valueOf 메소드에서 새로 인스턴스를 생성하지 않고, 미리 만들어둔 인스턴스를 재활용한다.
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

 

 


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

 

클래스의 생성자는 해당 클래스의 인스턴스만 만들 수 있다. 하지만 정적 팩토리 메소드를 사용하면 하위 클래스의 인스턴스까지 반환할 수 있다. 새로운 인터페이스와 수많은 구현 클래스가 있을 때, 구현 클래스의 생성자로 인스턴스를 만드는게 아니라 인터페이스의 정적 팩토리 메소드로 인스턴스를 만들어서 개발자가 수많은 구현 클래스들을 이해하지 않고도 인터페이스를 사용할 수 있도록 할 수 있다.

 

// List 인터페이스로 구현 클래스의 인스턴스를 만들었다.
List<Integer> list = List.of(1, 2, 3, 4, 5);

 

 


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

 

클래스의 생성자는 해당 클래스의 인스턴스만 만들 수 있다. 하지만 정적 팩토리 메소드를 사용하면 같은 메소드라도 상황에 따라 다른 클래스 인스턴스를 반환할 수 있다. 예를 들어 적은 메모리를 사용해야하는 경우와 그 반대의 경우에 따라 다른 클래스 인스턴스를 반환함으로써 자원을 효율적으로 사용할 수 있다.

 

// EnumSet의 정적 팩토리 메소드는 경우에 따라 RegularEnumSet, JumboEnumSet 두개의 클래스의 인스턴스를 반환한다.
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. 정적 팩토리 메소드를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 된다.

 

클래스가 존재해야 생성자가 존재할 수 있다. 하지만 정적 팩토리 메소드는 메소드와 반환할 타입만 정해두고 실제 반환될 클래스는 나중에 구현하는게 가능하다. 최근 프로그램의 규모는 점점 거대해지고 있고 여러 개발자/팀들이 협업하여 하나의 프로그램을 완성한다. 원활한 협업을 위해 인터페이스까지 먼저 합의하여 만들고, 실제 구현체는 추후 만드는 식으로 업무가 진행된다. 이때 정적 팩토리 메소드를 사용할 수 있다.

 

// nextProviderClass 메소드 작성 시점에는 클래스가 존재하지 않아도 된다.
// 클래스가 작성되면 그때 클래스의 이름을 전달하면 된다. 
private Class<?> nextProviderClass() {
	...
    return Class.forName(cn, false, loader);
    ...
}

 

 


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

 

정적 팩토리 메소드만을 사용하게 하려면 기존 생성자는 private으로 해야하고 상속을 할 수 없게된다. 하지만 이 단점은 상속보다 컴포지션 사용을 유도하고 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 장점으로 받아들일 수도 있다.


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

 

생성자는 기본 문법이기 때문에 개발자가 새로운 공부없이 바로 사용할 수 있다. 하지만 정적 팩토리 메소드는 개발자가 해당 클래스에 정적 팩토리 메소드가 있다는 사실을 알아야 한다. 이 단점을 완화라기 위해서는 널리 쓰이는 이름을 사용하여 정적 팩토리 메소드를 만들어야 한다.

 

// from: 매개변수 하나를 받아서 해당 타입의 인스턴스를 반환
Date d = Date.from(param);

// of: 매개변수 여러개를 받아서 적합한 타입의 인스턴스를 반환
Set<Rank> faceCards = EnumSet.of(param, param, param);

// valueOf: from, of와 유사
BigInteger bi = BigInteger.valueOf(param)

// instance, getInstnce: 매개변수에 맞는 인스턴스를 반환, 같은 인스턴스 보장 X
StackWalker luke = StackWalker.getInstance(param);

// create, newInstance: 매개변수에 맞는 인스턴스를 반환, 같은 인스턴스 보장 O
Object newArray = Array.newInstance(param)

// getType: getInstnce와 같으나 다른 클래스의 인스턴스를 반환
FileStore fs = Files.getFileStore(param);

// newType: newInstnce와 같으나 다른 클래스의 인스턴스를 반환
BufferedReader br = Files.newBufferedReader(param);

// type: getType, newType와 유사
List list = Collections.list(param);
728x90