점층적 생성자 패턴, 자바빈즈 패턴, 빌더 패턴 은 '빌더 패턴'의 우수성을 설명하기 위해 자주 비교된다.
public class Student {
private final String name; // 필수 매개변수
private final int age; // 필수 매개변수
private final String gradeClassNumber; // 필수 매개변수
private final String homeAddress; // 선택 매개변수
private final String phoneNumber; // 선택 매개변수
// 필수 매개변수를 가지는 생성자
public Student(String name, int age, String gradeClassNumber) {
this(name, age, gradeClassNumber, null, null);
}
// 필수 매개변수에 선택 매개변수 homeAddress 가 추가된 생성자
public Student(String name, int age, String gradeClassNumber, String homeAddress) {
this(name, age, gradeClassNumber, homeAddress, null);
}
// 모든 매개변수(필수 + 선택 + α)를 가지는 생성자
public Student(String name, int age, String gradeClassNumber, String homeAddress, String phoneNumber) {
this.name = name;
this.age = age;
this.gradeClassNumber = gradeClassNumber;
this.homeAddress = homeAddress;
this.phoneNumber = phoneNumber;
}
}
마치 생성자가 점층적으로 성장하는 생성자를 가지도록 한 디자인 패턴이 점층적 생성자 패턴
(필수 매개변수 → 필수 매개변수 + 선택 매개변수 → 모든 매개변수(필수 + 선택 + α))
필수 매개변수를 가지는 생성자를 필두로,
선택 매개변수를 가지는 생성자를 추가하여 여러 생성자를 가지는 디자인 패턴
객체의 불변성 유지
여러 개의 가변 인자(varargs)를 받을 수 있음
유연해서, 하나의 빌더 객체로 여러 객체를 만들 수 있음
제네릭을 이용하여 인자가 설정된 빌더는 훌륭한 추상화가 가능
코드 多
해당 값의 필드가 정확히 어떤 데이터를 지칭하는지 헷갈릴 수 있다
Student student = new Student("이진혁", 19, "3417", "경남 김해시")
매개변수의 타입이 동일할 경우, 생성자를 만들 수 없는 경우가 생긴다.
빌더 객체를 생성하는 오버헤드가 하드웨어와 밀접한 프로그램 or 성능이 중요한 상황에서는 큰 문제가 될 가능성
기존에 내가 사용하던 방식이다.
Student 엔티티
public class Student {
private final String name; // 필수 매개변수
private final int age; // 필수 매개변수
private final String gradeClassNumber; // 필수 매개변수
private String homeAddress; // 선택 매개변수
private String phoneNumber; // 선택 매개변수
public Student(String name, int age, String gradeClassNumber) {
this.name = name;
this.age = age;
this.gradeClassNumber = gradeClassNumber;
}
}
service
// setter로 선택 매개변수(homeAddress, phoneNumber)를 추가로 저장할 수도 있다.
Student student = new Student("이진혁", 19, "3417");
student.setHomeAddress("경남 김해시");
student.setPhoneNumber("010-1111-1111");
자바빈(Java Bean)처럼, 클래스에 setter를 추가함으로써 생성자 이후 setter로 필드 값을 채워넣으며 객체를 완성하는 디자인 패턴
인자 없는 생성자를 호출하여 기본 객체를 만든 다음, setter 를 이용하여 필드 값을 채워나가는 패턴
생성자에 전달되는 인자 수가 많을 때 적용 가능
(점층적 생성자 패턴에 비해) 기본값으로 초기화가 이루어진 상태에서 원하는 필드만 채워나가기 때문에,
설정할 필요가 없는 필드에 인자를 전달할 필요 X
(점층적 생성자 패턴에 비해) 각각의 필드의 값을 설정하는 setter 를 이용하기 때문에, 인자가 헷갈릴 일 X
객체의 일관성 보장 X
객체의 불변성 유지 X
객체 하나를 완성하기 위해, 여러 메서드를 호출해야 함
A a = new A();
a.setA("A");
a.setB(2);
a.freeze();
빌더 패턴은 매우 자주 사용된다.
'점층적 생성자 패턴의 안정성' + '자바빈 패턴의 가독성' 을 결합한 대안
작게 분리된 인스턴스를 건축 하듯이 조합하여 객체를 생성
필요한 객체를 직접 생성하는 대신, 필수 인자들을 생성자에 전부 전달하여 빌더 객체를 만든다.
그런 다음, 빌더 객체에 정의된 설정 메소드들을 호출하여 선택적 인자들을 추가해 나간다.
필요한 인자를 모두 적용하고 나면, build 메소드를 이용하여 변경 불가능(immutable) 객체를 만드는 패턴
인자가 많은 생성자나 정적 팩토리가 필요한 클래스를 설계할 때 유용.
setter 대신에 사용할 수 있다.
참고: Entity에서 @Setter 사용을 지양하자
Car 엔티티
class Car {
private String brand;
private String engine;
private String name;
private String tire;
private int capacity;
private int price;
Car (Builder builder) {
this.brand = builder.brand;
this.engine = builder.engine;
this.name = builder.name;
this.tire = builder.tire;
this.capacity = builder.capacity;
this.price = builder.price;
}
// Builder 클래스 : Car 클래스 안에, 정적 클래스인 Builder 를 만든다
public static class Builder {
private String brand;
private String engine;
private String name;
private String tire;
private int capacity;
private int price;
// 필수적인 필드 : brand
public Builder(String brand) {
this.brand = brand;
}
public Builder engine(String engine) {
this.engine = engine;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder tire(String tire) {
this.tire = tire;
return this;
}
public Builder capacity(int capacity) {
this.capacity = capacity;
return this;
}
public Builder price(int price) {
this.price = price;
return this;
}
// Builder 클래스는 build 메서드를 통해서 새로운 Car 객체를 생성
public Car build() {
return new Car(this);
}
}
}
service
// Builder 클래스는 build 메서드를 통해서 새로운 Car 객체를 생성
Car myCar = new Car.Builder("현대")
.engine("독일엔진")
.name("제네시스")
.tire("한국타이어")
.capacity(4)
.price(70000000)
.build();
@Builder
public class Bag {
private String name;
private int money;
private STring memo;
}
Bag bag = Bag.builder()
.name("name")
.money(1000)
.memo("memo")
.build();
@Builder.Default 사용 X
해당 필드의 값이 없다.
@Builder
@ToString
public class Member {
String id;
int age;
}
@Builder.Default 사용 O
age 필드에서 설정해둔 초기값이 출력된다.
@Builder
@ToString
public class Member {
String id;
@Builder.Default
int age = 10;
}
객체의 생성 과정과 표현 방법을 분리하여,
동일한 객체 생성에서도 서로 다른 결과를 만들어 낼 수 있다.(복잡한 객체 생성 가능)
여러 개의 가변 인자(varargs)를 받을 수 있음
제네릭을 이용하여 인자가 설정된 빌더는 훌륭한 추상화가 가능
(다른 패턴에 비해) 생성자 파라미터가 많아도 가독성이 좋음
// 빌더 패턴 X
Bag bag = new Bag("name", 1000, "memo", "abc", "what", "is", "it", "?");
// 빌더 패턴 O
Bag bag = Bag.builder()
.name("name")
.money(1000)
.memo("memo")
.letter("This is the letter")
.box("This is the box")
.build();
빌더의 필드 이름으로 값을 설정하기 때문에, 파라미터를 순서대로 두지 않아도 됨
// 빌더 패턴 X
public Bag(String name, int money, String memo) {
this.name = name;
this.money = money;
this.memo = memo;
}
// 빌더 패턴 O
Bag bag = Bag.builder()
.name("name")
.memo("memo") // memo를 money 대신 먼저해도 무방하다
.money(1000)
.build();
빌더 객체를 생성하는 오버헤드가
(점층적 생성자 패턴에 비해) 코드 多 필요