아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라
빌더 패턴이란?
빌더 패턴은 선택적 매개변수가 많은 상황에서 생성자 혹은 정적 팩토리 메서드보다 더 유용하게 사용할 수 있다.
필더 패턴을 이해하기 위해 예시로 가상의 가방(Bag) 객체를 만들 것이다.
가방을 주문하여 만들 때, 가방의 크기는 필수로 입력해야 하지만, 나머지 옵션들은 주문하는 사람이 마음대로 결정할 수 있다고 가정하자.
여기서 필수 매개변수는 가방의 사이즈가 될 것이고, 여러 옵션들은 매개변수가 될 것이다.
빌더 패턴 사용 전
빌더 패턴을 사용하기 전에는, 많은 선택적 매개변수를 처리하기 위해 아래 두 가지 패턴이 사용 된다.
1) 점층적 생성자 패턴 (Telescoping Constructor Pattern)
점층적 생성자 패턴은 필수 매개변수만 받는 생성자, 필수 매개변수 + 선택 매개변수 1개를 받는 생성자, 선택 매개변수를 2개까지 받는 생성자....과 같은 형태로 선택 매개변수를 전부 다 받는 생성자까지 늘려가는 방식이다.
점층적 생성자 패턴을 아래 코드로 보자면
public class Bag {
//필수 매개변수
private final int size;
//선택 매개변수
private final int zipper;
private final int pocket;
private final int handle;
public Bag(int size, int zipper, int pocket, int handle) {
this.size = size;
this.zipper = zipper;
this.pocket = pocket;
this.handle = handle;
}
public Bag(int size, int zipper, int pocket) {
this(size, zipper, pocket, 0);
}
public Bag(int size, int zipper) {
this(size, zipper, 0);
}
public Bag(int size) {
this(size, 0);
}
}
위 클래스를 사용하여 가방을 생성하는 코드는 아래와 같을 것 이다.
// 모든 옵션이 있는 가방
Bag bag1 = new Bag(50, 5, 4, 3, 2);
//손잡이와 주머니가 없는 가방
Bag bag2 = new Bag(50, 5);
//지퍼와 손잡이만 있는 가방
Bag bag3 = new Bag(50, 5, 0, 5);
여기서 지퍼와 손잡이만 있는 가방 bag3은 pocket이 필요 없다. 하지만, Bag 클래스에는 이를 생략할 방법이 구현되어 있지 않다.
사용자는 할 수 없이 직접 pocket 파라미터에 0을 전달해줘야 한다.
즉 점층적 생성자 패턴도 쓸 수는 있지만, 매개변수 개수가 많아지면 사용자가 코드를 작성하거나 읽기 어렵다.
또한 사용자가 설정하길 원치 않는 매개변수까지 포감되기 쉬운 형태이다. 따라서 실수로 잘못된 순서로 매개변수를 전달할 수 있다.
점층적 생성자 패턴은 이런 한계점을 가지고 있다.
2) 자바 빈즈 패턴 (JavaBeans Pattern)
자바 빈즈 패턴은 선택적 매개변수가 많을 때 활용할 수 있는 두번째 대안이다.
자바 빈즈 패턴은 처음 매개변수가 없는 생성자로 객체를 만들고, 세터(Setter) 메서드들을 호출하여 매개변수를 설정한다.
가방 클래스를 점층적 생성자 패턴 대신 자바빈즈 패턴으로 구현하면 아래와 같을 것이다.
public class Bag {
private int size = -1;
private int zipper = 0;
private int pocket = 0;
private int handle = 0;
public Bag () {}
public void setSize(int size) {
this.size = size;
}
public void setZipper(int zipper) {
this.zipper = zipper;
}
public void setPocket(int pocket){
this.pocket = pocket;
}
public void setHandle(int handle){
this.handle = handle;
}
}
위 클래스로 점층적 생성자 패턴에서 불편하게 생성했던 지퍼와 손잡이만 있는 가방을 생성해보면
Bag bag = new Bag();
bag.setSize(1);
bag.setZipeer(3);
bag.setHandle(4);
위 코드는 점층적 생성자 패턴에서의 단점은 보이지 않는다.
일관성 문제
개선된 점이 있음에도 여전히 문제점이 존재하는데 우선 1회의 함수 호출로 객체 생성을 끝낼 수 없으므로 객체의 일관성에 문제가 생긴다.
불변성 문제
또한 완전히 생성된 객체라고 하더라도 외부적으로 세터 메서드를 노출하고 있으므로, 불변함을 보장할 수 없게 된다.
즉, 자바 빈즈 패턴을 사용하면 클래스를 불변으로 만들 수 없다.
빌더 패턴의 사용
빌더 패턴은 별도의 객체를 통해 일련의 과정을 거쳐 객체를 생성하게 된다.
클래스 사용자는 직접 객체를 생성하지 않고, 필수 매개변수만으로 빌더 객체를 생성하고, 이 빌더 객체가 제공하는 일종의 세터 메서드를 사용하여 객체를 완성해갈 수 있다.
이때, 빌더 객체의 세터 메서드의 반환값은 빌더 자기 자신이므로 메서드를 연결하여 연속적으로 사용하는 메서드 체이닝을 할 수 있다.
코드로 나타내면 아래와 같다.
class Bag{
private final int size;
private final int zipper;
private final int pocket;
private final int handle;
private Bag(Builder builder){
this.size = builder.size;
this.zipper = builder.zipper;
this.pocket = builder.pocket;
this.handle = builder.handle;
}
public static class Builder {
// 필수 매개변수
private final int size;
// 선택 매개변수
private int zipper = 0;
private int pocket = 0;
private int handle = 0;
public Builder(int size) {
this.size = size;
}
public Builder zipper(int zipper) {
this.zipper = zipper;
return this;
}
public Builder pocket(int pocket) {
this.pocket = pocket;
return this;
}
public Builder handle(int handle) {
this.handle = handle;
return this;
}
}
}
Bag 클래스 내부에 정적 클래스인 Builder가 정의되어 있는 모습을 볼 수 있다.
Builder 클래스는 필수 매개변수인 size만을 전달받아 생성된다. 그리고 zipper, pocket, handle과 같은 메서드는 빌더 자기 자신의 매개변수를 설정하고, 자기자신을 반환하는 것을 확인할 수 있다.
마지막으로 build 메소드는 Bag 객체를 생성하여 반환하는데, Bag의 매개변수에는 빌더 하나만 전달한다. Bag클래스의 생성자는 Builder를 전달받아 매개변수를 설정할 수 있다.
Bag bag = new Bag.Builder(10)
.zipper(4)
.pocket(3)
.handle(2)
.build();
빌더 패턴은 점층적 생성자 + 자바빈즈 패턴 두 가지의 장점만을 취한 형태이다.
빌더 패턴의 단점
빌더 패턴도 장점만 존재하지는 않다. 선택적 매개변수를 많이 받는 객체를 생성하기 위해서는 먼저 빌더 클래스부터 정의해야한다. 빌더의 생성비용이 크지는 않지만, 성능에 민감한 상황에서는 문제가 될 수 있다고 한다.
또한 매개변수가 4개보다 적다면 (예시는 어쩔 수 없이..) 점층적 생성자 패턴을 사용하는 것이 좋다고 한다.
빌더 패턴의 코드가 다소 장황하기 때문이다. 하지만 API는 시간이 지날수록 많은 매개변수를 갖는 경향이 있다.
따라서 애초에 빌더 패턴으로 시작하는 편이 더 좋을 것이다!
참고 자료 블로그
https://ocwokocw.tistory.com/187
이펙티브 자바 - Builder 패턴
- 이 글은 Effective Java 를 기반으로 작성되었습니다. - Builder 패턴 정적 팩토리 메소드나 생성자는 선택적 인자가 많을 때 사용하기가 난감하다. 빌더 패턴은 이런 문제를 해결하기 위해 사용하는
ocwokocw.tistory.com
https://hudi.blog/effective-java-builder-pattern/
'도서 > 정리' 카테고리의 다른 글
[이펙티브 자바] 아이템6. 불필요한 객체 생성을 피하라 (0) | 2023.05.15 |
---|---|
[이펙티브 자바] 아이템5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2023.05.09 |
[이펙티브 자바] 아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (1) | 2023.05.07 |
[이펙티브 자바] 아이템3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2023.05.05 |
[이펙티브 자바] 아이템1. 생성자 대신 정적 팩토리 메서드를 고려하라 (0) | 2023.05.03 |