IT/Java

Effective java: 생성자 대신 정적 팩터리 메서드 요약

Dev. Sean 2024. 1. 23. 07:51
반응형

많은 개발자들이 객체를 생성할 때 주로 생성자를 사용한다.
하지만 정적 팩터리 메서드라는 또 다른 훌륭한 방법이 존재한다.

먼저, 전통적인 생성자 방식을 살펴보자.
'음료' 클래스를 예로 들어보겠다.

 

public class Beverage {

    private String type;
    private boolean isCold;

    public Beverage(String type, boolean isCold) {
        this.type = type;
        this.isCold = isCold;
    }

    // ... 기타 메서드
}

 

여기서 `Beverage` 클래스는 하나의 생성자를 가지고 있다.
사용자는 이 생성자를 통해 '음료' 객체를 생성할 수 있다.

예를 들어, 뜨거운 커피를 만들고 싶다면 다음과 같이 사용할 수 있다.

Beverage hotCoffee = new Beverage("Coffee", false);

 

이제 정적 팩터리 메서드를 살펴보자. 같은 '음료' 클래스에 정적 팩터리 메서드를 추가해 보겠다.

 

public class Beverage {

    // 기존 생성자와 필드는 유지

    public static Beverage createHotBeverage(String type) {
        return new Beverage(type, false);
    }

    // ... 기타 메서드
}
```

 


이제 '뜨거운 음료'를 생성하는 경우 `createHotBeverage` 메서드를 사용할 수 있다.
이 메서드는 명시적이고, 사용하기 쉽다.

 

Beverage hotCoffee = Beverage.createHotBeverage("Coffee");

 


정적 팩터리 메서드는 사용자에게 명확한 의도를 전달하고, 필요에 따라 인스턴스의 재사용이 가능하며, 다양한 서브클래스의 객체를 반환할 수 있는 유연성을 제공한다.
하지만, 이 메서드는 상속을 위해 public이나 protected 생성자가 필요하며, API 문서에서 바로 보이지 않는다는 단점이 있다.

 

 

정적 팩터리 메서드의 장점

1. 명시적인 이름을 가질 수 있다
   - 예시: `ComplexNumber` 클래스에서, `ComplexNumber.fromPolar(double magnitude, double angle)` 메서드는 `new ComplexNumber(magnitude, angle)`보다 훨씬 명확하게 극좌표를 사용하여 복소수를 생성한다는 것을 나타낸다.

2. 호출될 때마다 새로운 인스턴스를 생성하지 않고 기존 인스턴스를 재사용할 수 있다
   - 예시: `Integer.valueOf(int)` 메서드는 `-128`에서 `127` 범위의 정수에 대해 미리 생성된 인스턴스를 반환한다.
이는 빈번하게 사용되는 작은 정수 값에 대해 새로운 `Integer` 객체를 매번 생성하는 것을 방지한다.

3. 하위 타입의 객체를 반환할 수 있는 유연성을 제공한다
   - 예시: `Java Collections API`에서, `Collections.unmodifiableList(List)` 메서드는 리스트의 불변 래퍼를 반환한다.
이 메서드는 `List` 인터페이스를 구현하는 어떤 객체도 받아들일 수 있으며, 사용자는 반환되는 구체적인 클래스를 몰라도 된다.

4. 입력 매개변수에 따라 다른 클래스의 객체를 반환할 수 있다
   - 예시: `EnumSet`의 정적 팩터리 메서드들은 원소의 수에 따라 다른 구현을 가진 `EnumSet` 인스턴스를 반환한다. 소수의 원소를 가진 경우, 비트 필드를 사용하는 효율적인 구현을 사용하고, 그렇지 않은 경우 더 일반적인 구현을 사용한다.

5. 반환할 객체의 클래스가 실행 시점에 존재하지 않아도 된다
   - 예시: JDBC API에서, `DriverManager.getConnection(String url)` 메서드는 URL에 따라 다른 데이터베이스 드라이버의 `Connection` 인스턴스를 반환한다.
이 방식은 특정 데이터베이스 드라이버에 대한 의존성을 줄이고, 실행 시점에 적절한 드라이버를 선택할 수 있게 한다.

 

 

정적 팩터리 메서드의 단점과 상세한 예시


1. 상속에 제한
   - 예를 들어, `Beverage` 클래스가 `createHotBeverage`와 같은 정적 팩터리 메서드만 제공하고 생성자가 `private`인 경우, 다른 클래스가 `Beverage`를 상속받아 확장하는 것이 불가능하다.
   - 예시:

public class Beverage {
         private String type;

         private Beverage(String type) {
             this.type = type;
         }

         public static Beverage createBeverage(String type) {
             return new Beverage(type);
         }
     }

     public class Coffee extends Beverage { // 컴파일 오류 발생
         public Coffee() {
             super("Coffee");
         }
     }


     여기서 `Coffee` 클래스는 `Beverage` 클래스를 확장하려 하지만, `Beverage`의 생성자가 `private`이기 때문에 `Coffee` 클래스는 컴파일 오류를 발생시킨다.

2. API 문서에서의 눈에 띄지 않음
   - 생성자는 자바 문서에서 쉽게 찾을 수 있지만, 정적 팩터리 메서드는 그렇지 않다.
개발자가 `Beverage` 클래스의 API 문서를 보더라도, `createHotBeverage`와 같은 메서드가 존재한다는 것을 쉽게 알아차리기 어렵다.
   - 예시:
     - `Beverage` 클래스의 API 문서에는 `createHotBeverage` 메서드가 생성자처럼 명확하게 보이지 않을 수 있어, 개발자가 이 메서드를 간과할 가능성이 있다.

이러한 단점들에도 불구하고, 정적 팩터리 메서드는 여전히 유용한 도구다.
하지만 이러한 제한과 문제점들을 염두에 두고, 상황에 맞게 적절한 객체 생성 방식을 선택하는 것이 중요하다.

반응형