많은 개발자들이 객체를 생성할 때 주로 생성자를 사용한다.
하지만 정적 팩터리 메서드라는 또 다른 훌륭한 방법이 존재한다.
먼저, 전통적인 생성자 방식을 살펴보자.
'음료' 클래스를 예로 들어보겠다.
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` 메서드가 생성자처럼 명확하게 보이지 않을 수 있어, 개발자가 이 메서드를 간과할 가능성이 있다.
이러한 단점들에도 불구하고, 정적 팩터리 메서드는 여전히 유용한 도구다.
하지만 이러한 제한과 문제점들을 염두에 두고, 상황에 맞게 적절한 객체 생성 방식을 선택하는 것이 중요하다.
'IT > Java' 카테고리의 다른 글
Effective Java: 생성자에 많은 매개변수가 필요할 때: 빌더 패턴 고려하기 (0) | 2024.01.24 |
---|---|
Spring에서 CORS 설정하기 (0) | 2024.01.23 |
Integer와 Long의 차이점 (0) | 2024.01.20 |
스프링 부트: 윈도우와 맥에서 한글 파일명 다운로드 문제 해결하기 (1) | 2024.01.20 |
warning: unknown enum constant When.MAYBEreason: class file for javax.annotation.meta.When not found (0) | 2024.01.19 |