본문 바로가기
Java

[Effective Java] Item01. 생성자 대신 정적 팩토리 메서드를 고려하라

by 긍정의고등어 2022. 1. 10.

들어가며

최근 회사에서 인턴 멘토링을 하게 되며 길게 늘어지는 빌더의 사용 대신 정적 팩토리 메서드의 사용을 추천한 적이 있다. 평소 빌더 패턴을 이용한 객체 생성보다는 정적 팩토리 메서드 패턴을 이용한 객체 생성을 즐겨 쓰던 터라 관련 코멘트를 남겼지만, 코멘트를 남기다 보니 빌더 패턴에 비해 정적 팩토리 메서드 패턴이 갖는 명확한 장점이 무엇인지 나 스스로도 모호하다고 생각했다.

 

관련 자료를 다시 찾아보고 정리한 바에 따르면 인스턴스 생성에 필요한 인자가 많고 자주 변한다면 빌더 패턴을 사용하는 것이 좋으나, 매 인스턴스 생성시마다 사용하는 인자가 정해져있는 상황에서는 정적 팩토리 메서드 패턴이 더 장점을 갖는 것 같다. 이와 관련해서 이번 포스트, 다음 포스트에서 각 패턴의 특징을 정리해보려고 한다.

자바 클래스의 인스턴스를 얻는 방법

자바에서 어떠한 클래스의 인스턴스를 얻기 위해 사용하는 가장 범용적인 방법은 public 생성자를 이용하는 것이다. 이러한 생성자 외에 인스턴스를 얻는 방법은 한 가지 더 있는데 클래스에 인스턴스를 반환하는 static 메서드를 만드는 것이다. 정적 팩토리 메서드를 이용한 인스턴스 생성 방식은 다음과 같은 장점, 단점을 갖는다.

정적 팩토리 메서드의 장점

1. 이름을 가질 수 있다.

class Person {

	private String name;
	private Double height;

	public Person() {
	}

	public Person(String name, Double height) {
		this.name = name;
		this.height = height;
	{

	public static Person nameAndHeightOf(String name, Double height) {
		return new Person(name, height);
	}

}

위 예제에서 public 생성자를 이용하여 Person 인스턴스를 생성하려면 Person person = new Person("이름", 160.6) 과 같이 사용하여야 한다. 이러한 인스턴스 생성 기법은 코드만 보고서는 뒤의 숫자가 어떤 항목을 뜻하는지 알기 어렵다. 도메인이 복잡해질 수록 이러한 현상은 심화될 것이며 이는 정적 팩토리 메서드의 이름을 통해서 해결할 수 있다.

 

아래 부분의 정적 팩토리 메서드를 이용하여 Person 인스턴스를 생성한다면 Person person = Person.nameAndHeightOf("이름", 160.6) 과 같이 사용할 수 있으며 이 경우에는 정적 팩토리 메서드의 이름(이름을 짓는 몇 가지 규칙을 뒤에서 소개한다)을 통해 호출에 사용되는 인자가 어떤 정보인지는 한 눈에 파악할 수 있다.

 

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

다음은 Boolean의 valueOf 라는 정적 팩토리 메서드의 내부 코드이다.

public static Boolean valueOf(boolean b) {
	return b ? Boolean.TRUE : Boolean.FALSE;
}

코드 내부를 보면 원시값 b에 따라 미리 만들어 둔 TRUE, FALSE 중 하나의 값(인스턴스)을 리턴하는 것을 볼 수 있다. 정적 팩토리 메서드를 사용하면 매번 새로운 인스턴스를 반환할 수 있지만 이처럼 미리 만들어둔 인스턴스를 반환할 수 있게되면서 메모리 낭비를 줄일 수도 있다.

 

3. 반환 타입의 하위 객체 타입을 반환할 수 있다.

이러한 특징을 이용하면 구현 클래스의 공개 없이 그 객체를 반환할 수 있으므로 API를 작게 유지할 수 있다. 그 예로는 java.util.Collections가 있다.

public class Collections {

	...
	public class <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) {
		return new UnmodifiableCollection<>(c);
	}

	...
	static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
		...
	}

}

 

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

반환 타입은 해당 타입의 하위 클래스이기만 하면 되기 때문에 어떤 클래스의 객체를 반환받는지 클라이언트는 알 필요가 없고 철저히 인터페이스에 의존한 개발을 할 수 있게 된다.

public interface Person {
	static Person of(int y) {
		Person instance;

		if (y > 50) {
			instance = new Old(y);
		} else {
			instance = new Young(y);
		}

		return instance;
	}

	String getGeneration();

	class Young implements Person {
		private int value;

		Young(int y) {
			this.value = y;
		}

		@Override
		public String getGeneration() {
			return "청년";
		}
	}

	class Old implements Person {
		private int value;

		Old(int y) {
			this.value = y;
		}

		@Override
		public String getGeneration() {
			return "중장년";
		}
	}
}

위 예제에서 Person은 정적 팩토리 메서드에 주어진 나이 인자에 따라 Old 혹은 Young의 인스턴스를 반환받게 된다. 하지만 두 인스턴스 모두 세대 정보를 얻을 수 있는 getGeneration() 메소드를 포함하고 있기 때문에 클라이언트는 어떤 클래스의 인스턴스인지는 신경쓰지 않고 Person 인터페이스에 의존한 개발을 할 수 있다.

Person person = Person.of(27);
String generation = person.getGeneration();

Person person1 = Person.of(56);
String generation1 = person1.getGeneration();
// Person 인스턴스가 Young이든 Old이든 신경쓰지 않고 getGeneration 호출 가능

정적 팩토리 메서드의 단점

정적 팩토리 메서드는 위에서와 같이 많은 장점을 가지고 있지만 단점 또한 존재한다.

 

1. 생성자 없이 정적 팩토리 메서드만 제공한다면 상속을 할 수 없다.

상속은 public 혹은 protected 생성자가 필요한데, 이러한 생성자 없이 팩토리 메서드만 제공한다면 상속을 할 수 없는 문제가 발생한다.

 

2. 개발자가 찾기 어렵다.

일반적인 생성자는 javadoc을 통해 쉽게 찾을 수 있는 반면 정적 팩토리 메서드는 javadoc에서 쉽게 확인할 수 없다. 따라서 개발자는 코드를 보며 정적 팩토리 메서드 방식 클래스를 인스턴스화 할 방법을 찾아야 한다.

이러한 혼동을 막기 위해 정적 팩토리 메서드에는 일반적인 네이밍 규칙이 존재한다.

이름 설명

from 매개변수를 1개 받아 해당 타입의 인스턴스 반환
of 매개변수를 여러 개 받아 적합한 타입의 인스턴스 반환
valueOf from, of의 자세한 버전
instance / getInstance 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않음
create / newInstance 위와 같지만 매번 새로운 인스턴스 반환
getType getInstance와 동일, 다른 클래스에 팩토리 메서드 정의. Type은 반환할 객체 타입
newType newInstance와 동일, 다른 클래스에 팩토리 메서드 정의. Type은 반환할 객체 타입
type getType과 newType의 간결 버전

 

정리

정적 팩토리 메서드와 public 생성자 방식은 각자의 장단점이 있으니 잘 파악하고 사용하는 것이 좋다. 하지만 상속을 필요치 않는 보통의 경우 정적 팩토리 메서드 방식의 장점이 많으니 관성적으로 public 생성자를 사용하던 습관이 있으면 그러한 습관을 되돌아보고 고치는 것이 좋다.

댓글