생성자에 선택적 매개변수가 많다면 어떻게 해결할까?
1) 점층적 생성자 패턴(telescoping constructor pattern)
피자를 만든다고 생각해보자. (= Pizza Class가 있다.)
피자를 만드는데 사이즈와 조각 수는 필수이다. (=size와 piece는 필수 매개변수이다. )
피자를 만드는데 햄, 치즈, 고기, 소스는 선택사항이다. (=ham, cheese, beef, sauce 는 선택적 매개변수다.)
이 클래스에서 우리는 생성자를 다음과 같이 구현 할 수 있다.
‘필수 매개변수만 받는 생성자’ - (size, piece )
‘필수 매개변수와 선택 매개변수 1개를 받는 생성자’ - (size, piece, ham)
‘필수 매개변수와 선택 매개변수 2개를 받는 생성자’ - (size, piece, ham, cheese)
…….
‘필수 매개변수와 선택 매개변수 전체를 받는 생성자’ -(size, piece, ham, cheese, beef, sauce)
이런식으로 선택 매개변수가 없는 생성자부터 선택 매개변수를 전부 받는 생성자까지 늘려나가는 방식을 ‘점층적 생성자 패턴’ 이라고 한다.
만약 ( Size : 10, Piece : 4, Beef : 20 )인 Pizza 객체를 만들어야 한다면 다음과 같이 객체를 생성할 수 있다.
<Ex 1.1>
< 장점 >
( 10, 5 , 햄 없음, 치즈 없음, 고기 없음, 소스 없음 ) 과 같은 Pizza 객체를 생성할 때
new Pizza(10,5) 으로 쉽게 생성할 수 있다.
< 단점 >
<Ex1.1> 객체를 만들기위해 ham과 cheese에 0을 넣어줘야하는 불편함이 생긴다.
현재는 beef 앞에 사용하지 않는 선택 매개변수가 햄과 치즈 2개 밖에 없지만, 프로그램을 짜다보면 그 수는 훨 씬 늘어난다.
따라서 점층적 생성자 패턴을 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다. 코드를 읽을 때 각 값의 의미가 무엇인지 헷갈릴 수 있고, 매개변수가 몇 개 인지도 주의해서 세어 보아야 할 것이다.
2) 자바빈즈 패턴 (JavaBeans pattern)
자바빈즈 패턴은 매개변수가 없는 생성자로 객체를 만든 후, 세터 메서드를 호출해 원하는 매개변수의 값을 설정하는 방식이다.
점층적 생성자 패턴 구현했던 피자 클래스를 자바빈즈 패턴으로 변환한 아래의 코드를 보자.
점층적 생성자 패턴에서 봤던 피자 클래스가 위와 같이 바뀌었다.
매개변수가 없는 생성자 1개와 Setter 6개로 구현된 클래스이다.
사용 :
점층적 생성자 패턴의 단점은 사라지고, 인스턴스를 만들기 쉽고, 더 읽기 쉬운 코드가 되었다.
하지만 위의 자바빈즈 패턴은 심각한 단점이 있다. 뭘까?
자바빈즈 패턴의 단점.
(1). 객체 하나를 만드려면 메서드를 여러개 호출해야한다.
위의 ‘사용’ 예에서 Setter 메서드가 6개나 호출된 것을 볼 수 있다.
따라서 1회의 호출로 객체 생성이 끝나지 않으므로 객체의 일관성(Consistency)이 깨진다.
(2) 일관성이 무너지는 문제 때문에 클래스를 불변으로 만들 수 없으며, 스레드 안전성을 얻으려면 프로그래머가 추가 작업을 해줘야만 한다.
3) 빌더 패턴
점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 빌더 패턴
클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.
그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다.
마지막으로 매개변수가 없는 build 메서드를 호출해 드디어 우리에게 필요한 객체를 얻는다.
→ 빌더는 생성할 클래스 안에 정적 멤버 클래스로 만들어두는 게 보통이다.
앞의 예제를 빌더 패턴으로 구현한 코드를 예로 보도록 하자.
Pizza 클래스 내에 정적 멤버 클래스로 Builder 클래스가 있는 구조이다.
Builder 클래스는 필수 매개변수를 가진 생성자가 있다.
그리고 Builder의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다.
사용 예를 보자.
클라이언트는 위와 같이 두 가지 방법으로 사용할 수 있다.
이 클라이언트 코드는 쓰기 쉽고, 읽기 쉽다. 빌더 패턴은 명명된 선택적 매개변수를 흉내낸 것이다.(파이썬과 스칼라에 존재하는)
☞ 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
각 계층의 클래스에 관련 빌더를 멤버로 정의한다.
추상 클래스는 추상 빌더를, 구체 클래스(concrete class)는 구체 빌더를 갖게 한다.
나는 클래스를 계층적으로 설계해본 경험이 부족해서 책에 있는 예제를 보여주고 설명하겠다.
<abstract class Pizza>
< 뉴욕피자 >
Pizza 클래스의 하위 클래스인 뉴욕피자 클래스다.
뉴욕피자는 크기를 필수 매개변수로 받는다.
<칼초네 피자>
Pizza 클래스의 하위 클래스인 칼초네(Calzone) 클래스다.
Pizza 클래스는 소스를 넣을지 않넣을지 선택하는 매개변수를 필수로 받는다.
각 하위 클래스의 빌더가 정의한 build 메서드는 해당하는 구체 하위 클래스를 반환하도록 한다. ( NyPizza.Builder는 NyPizza, Calzone,Builder는 Calzone를 반환)
이렇게 하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환타입이 아닌, 그 하위 타입을 반환하는 기능을 ‘공변환 반환 타이핑’이라 한다. 이 기능을 사용하면 클라이언트가 형변환에 신경쓰지 않고도 빌더를 사용할 수 있다.
사용 예)
빌더 패턴의 단점
객체를 만드려면 빌더부터 만들어야한다.
(빌더 생성 비용이 크지는 않지만 민감한 상황에서는 문제가 될 수 있음)
점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다.
→ 하지만 API는 시간이 지날수록 매개변수가 많아지는 경향이 있음.
정리.
생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다.
특히 매개변수 중 다수가 필수가 아니거나 같은 타입인 경우에.
빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.
'Java' 카테고리의 다른 글
정적 펙터리 메서드 (static factory method) (0) | 2019.03.27 |
---|---|
Annotation (어노테이션) (0) | 2018.11.13 |