JAVA/Effective Java

[아이템 1] 생성자 대신 정적 팩토리 메서드를 고려하라

꾸준함. 2024. 1. 16. 22:01

생성자 대신 정적 팩토리 메서드를 사용할 경우 얻을 수 있는 장단점은 아래와 같습니다.

 

장점

  • 메서드명 즉 이름을 가질 수 있기 때문에 표현력이 높아짐
  • 호출될 때마다 새로운 인스턴스를 생성하지 않아도 됨
  • 반환 타입의 하위 타입 객체를 반환 가능 (인터페이스 기반 프레임워크)
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환 가능
  • 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재 여부를 고려하지 않아도 됨

 

단점

  • 정적 팩토리 메서드만 제공할 경우 생성자를 private으로 선언하기 때문에 상속 불가능
  • 역설적으로 이름을 가지기 때문에 생성자 대비 정적 팩토리 메서드 조회하기 번거로움

 

장점 1. 메서드명 즉 이름을 가질 수 있기 때문에 표현력이 높아짐

병원에 환자가 내원할 때 아래와 같이 두 가지 타입이 있다고 가정하겠습니다.

  • 긴급
  • 일반

 

일반적으로 위와 같은 경우 클래스 멤버 변수로 boolean urgent와 같이 하나의 변수로 표현하겠지만 이번 예제에서는 아래와 같이 굳이 각각을 표현하는 boolean 변수가 있다고 가정하겠습니다.

 

 

 

시그니처는 생성자의 매개변수 타입까지만 보고 urgent와 normal 모두 타입이 boolean이기 때문에 생성자로는 두 환자 타입을 표현하는데 한계가 있습니다.

물론 우회하고자 생성자 매개변수 순서를 변경할 수도 있겠지만 생성자는 이름을 표현할 수 없고 클래스명과 동일하기 때문에 표현하는데 한계가 존재합니다.

 

 

 

 

반면, 정적 팩토리 메서드를 사용할 경우 메서드명을 통해 아래와 같이 표현을 잘해줄 수 있다는 장점이 있습니다.

 

 

 

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

정적 팩토리 메서드를 사용할 경우 객체의 생성 로직을 메서드를 통해 통제가 가능합니다.

디자인 패턴 중 하나인 Flyweight Pattern에서 사용되는 기믹으로 자주 사용하는 값을 미리 캐싱해서 넣어두고 꺼내오는 방식입니다.

 

 

 

실제로 Boolean 클래스의 valueOf 메서드를 보면 정적 팩토리 메서드를 통해 Flyweight Pattern이 적용된 것을 확인할 수 있습니다.

 

 

 

장점 3. 반환 타입의 하위 타입 객체를 반환 가능 (인터페이스 기반 프레임워크)

생성자의 경우 해당하는 클래스 인스턴스만 가져올 수 있어 고정적이지만 정적 팩토리 메서드는 유연하기 때문에 반환 타입이 인터페이스일 때 해당 인터페이스 구현체 중 아무거나 반환할 수 있습니다.

 

 

 

 

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

장점 3에서 작성한 내용과 비슷한데 정적 팩토리 메서드 사용 시 메서드가 반환하는 구체적인 객체 타입을 숨길 수 있습니다.

자바 8 버전부터는 인터페이스에서 static 메서드 선언 및 static 메서드 선언이 가능하기 때문에 factory 클래스 없이 해당 인터페이스에서 정적 팩토리 메서드를 생성 및 반환할 수 있습니다.

자바 9 버전부터는 private static 메서드를 가질 수 있지만 private 필드는 아쉽게도 선언할 수 없습니다.

 

 

 

장점 5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재 여부를 고려하지 않아도 됨

java.util에서 제공하는 ServiceLoader 클래스를 활용할 경우 특정 구현체에 의존적이지 않은 코드를 구현할 수 있습니다.

메서드를 제공하는 입장에서는 구체적인 타입(하위 타입) 객체를 숨길 수 있다는 장점과 어떤 구현체가 올지 모르지만 구현체가 따르는 인터페이스를 구현하면 되는 유연함을 제공한다는 장점이 있습니다.

 

 

 

단점 1. 정적 팩토리 메서드만 제공할 경우 생성자를 private으로 선언하기 때문에 상속 불가능

사실 이는 단점이자 장점일 수 있습니다.

앞서 장점 1에서 다룬 예제를 복습하면 생성자를 private으로 선언하여 정적 팩토리 메서드를 통해서만 인스턴스를 생성하도록 강제했습니다.

상속을 하려면 public이나 protected 생성자가 필요하기 때문에 명시적인 상속을 할 수 없지만 상속이 필요한 클래스에 해당 클래스를 멤버 변수로 지니게 하면 상속을 우회할 수 있습니다.

 

 

 

그리고 어찌 보면 이러한 제약이 상속보다 컴포지션을 사용하도록 유도하고 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점일 수도 있습니다.

 

단점 2. 역설적으로 이름을 가지기 때문에 생성자 대비 정적 팩토리 메서드 조회하기 번거로움

mvn javadoc:javadoc 명령어를 호출하면 아래와 같이 자바 클래스에 대한 명세서를 생성할 수 있습니다.

https://docs.oracle.com/javase/8/docs/api/java/lang/Boolean.html

 

Boolean (Java Platform SE 8 )

The Boolean class wraps a value of the primitive type boolean in an object. An object of type Boolean contains a single field whose type is boolean. In addition, this class provides many methods for converting a boolean to a String and a String to a boolea

docs.oracle.com

 

그리고 생성자가 명시적으로 선언되어 있을 경우 아래와 같이 javadoc에서 바로 확인할 수 있습니다.

 

 

하지만 정적 팩토리 메서드를 통해서만 인스턴스를 생성할 수 있도록 강제할 경우 개발자가 해당 객체를 생성하기 위해 어떤 메서드를 호출해야 할지 javadoc에서 찾기가 상대적으로 어려울 수 있습니다.

위 문제를 해결하기 위해 정적 팩토리 메서드는 보통 아래와 같이 널리 알려진 규약을 따라 메서드명을 짓는 식으로 해결을 진행하고 있습니다.

 

메서드명: from

  • 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드

 

 

메서드명: of

  • 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드


 

메서드명: valueOf

  • from과 of의 더 자세한 버전


 

메서드명: instance, getInstance

  • (매개변수를 받을 경우) 매개변수로 명시한 인스턴스를 반환하지만 같은 인스턴스임을 보장하지는 않음


 

메서드명: create, newInstance

  • 매번 새로운 인스턴스를 생성해 반환함을 보장


 

메서드명: getType

  • getInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용
  • "Type"은 팩토리 메서드가 반환할 객체의 타입


 

메서드명: newType

  • newInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용
  • "Type"은 팩토리 메서드가 반환할 객체의 타입


 

메서드명: type

  • getType과 newType의 간결한 버전


 

정리

위에서 정리한 것처럼 정적 팩토리 메서드와 public 생성자는 각자의 쓰임새가 있기 때문에 상대적인 장단점을 이해하는 것이 중요합니다.

많은 케이스에서 정적 팩토리 메서드를 사용하는 것이 유리한 경우가 많기 때문에 무작정 public 생성자를 제공하던 습관을 고치고 클래스마다 어떤 방식을 따를지 고민하는 자세를 가지면 좋을 것 같습니다.

 

참고

이펙티브 자바

이펙티브 자바 완벽 공략 1부 - 백기선 강사님

반응형