빌더는 복잡한 object들을 단계별로 구축할 수 있는 생성 디자인 패턴.
이 패턴을 사용하면, 동일한 구성코드를 사용하여 다양한 타입과 표현을 제공함
=> 객체를 잘 만들 수 있게 해주는 도구
객체를 생성하는 방법
1. 생성자에 인자를 넣어 인스턴스를 생성한다
2. SETTER 사용한 자바빈 패턴
이 있는데, 단점을 개선한 방법으로
3. 빌더 패턴
이 존재한다
public class Pizza {
private String name;
private String dough;
private String sauce;
private String topping;
private int price;
public Pizza(String name, int price) {
this.name = name;
this.price = price;
}
public Pizza (String name, String dough, String sauce, int price) {
this.name = name;
this.dough = dough;
this.sauce = sauce;
this.price = price;
}
public Pizza(String name, String dough, String sauce, String topping, int price) {
this.name = name;
this.dough = dough;
this.sauce = sauce;
this.topping = topping;
this.price = price;
}
@Override
public String toString() {
return "name: " + name + ", " + "dough: " + dough + ", " + "sauce: " + sauce + ", " + "topping: " + topping + "price: " + price;
}
}
피자에 필수 인자인 name, price만 인자로 받는 pizza 생성자를 선언
name, price, dough, sauce를 추가하는 생성자 선언
나머지 모든 인자 값을 사용하는 피자 생성자도 만들었음
이 방식의 장점
-> 하나의 메서드를 통해서 객체의 생성과 값 설정을 통해 완성된 객체를 반환할 수 있어 일관성을 지킬 수 있음
이 방식의 단점
-> new Pizza("슈프림 피자", "씬도우", "핫소스", 5000)같이 호출이 많은 경우 그만큼 생성자를 많이 만들어야 함.
-> 객체의 프로퍼티가 많으면 그에 따른 생성자도 많이 만들어야 함
-> 인자에 대한 설명이 없기 때문에 인자가 많은 경우에 헷갈림
-> 설정하지 않는 값을 null로 채워서 사용해야 함
-> 변수를 어느 위치에 세팅해야하는지 알기 어려움.
등이 있음
public class PizzaJavaBean {
private String name;
private String topping;
private int price;
public PizzaJavaBean() {
// 기본 생성자
}
public String getName() {
return name;
}
public String getTopping() {
return topping;
}
public int getPrice() {
return price;
}
public void setName(String name) {
this.name = name;
}
public void setTopping(String topping) {
this.topping = topping;
}
public void setPrice(int price) {
this.price = price;
}
}
생성자에 인자를 넣어 인스턴스를 생성하는 방법을 보완해서 만든 방법. setter를 사용하여 생성한 객체에 인자를 정확하게 파악하여 세팅함
장점
-> 각 인자의 의미를 setter 메서드에 맞는 값을 넣기 때문에 정확하게 파악이 가능
-> 객체 클래스 내부에 복잡하게 여러 개의 생성자를 만들 필요가 없음
단점
=> 객체가 완전히 생성되기 전까지는 '일관성이 무너진 상태에 놓이게 된다'라고 말함
Pizza pizza = new Pizza();
//pizza.setName("dilicious");
//pizza.setTopping("olive");
//pizza.setPrice("10000");
주석처리 되어도 실행에는 문제가 없음 - 컴파일 시점에서 오류로 잡을 수 없음 - 고객들이 회원가입을 할 때 오류를 겪을 수 있음 == 일관성이 불안정한 상태로도 사용이 가능.
-> public으로 선언되어있기 때문에 정보의 일관성을 지키기 힘듦
-> 1회 호출로 끝나지 않고 setter를 통해 값을 계속 세팅해줘야 함, 이 때 값을 초기 설정하기 위한 것인지 변경하기 위한 것인지 알 수 없음.
public class PizzaBuilder {
private final String name;
private final String dough;
private final String sauce;
private final String topping;
private final int price;
// 객체 생성 전, 값을 세팅해줄 Builder 내부 클래스
public static class Builder {
private String name;
private String dough;
private String sauce;
private String topping;
private int price;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder dough(String dough) {
this.dough = dough;
return this;
}
public Builder sauce(String sauce) {
this.sauce = sauce;
return this;
}
public Builder topping(String topping) {
this.topping = topping;
return this;
}
public Builder price(int price) {
this.price = price;
return this;
}
// 값 세팅이 끝난 후 내부 클래스를 넘겨주어 본 객체에 값을 세팅해주는 메소드
public PizzaBuilder build() {
return new PizzaBuilder(this);
}
}
// 값 세팅이 끝난 후 내부 클래스를 넘겨주어 본 객체에 값을 세팅해주는 메서드
public PizzaBuilder(Builder builder) {
this.name = builder.name;
this.dough = builder.dough;
this.sauce = builder.sauce;
this.topping = builder.topping;
this.price = builder.price;
}
@Override
public String toString() {
return "name: " + name + ", " + "dough: " + dough + ", " + "sauce: " + sauce + ", " + "topping: " + topping + "price: " + price;
}
}
사용 방법
public class Main {
// 빌더 패턴 사용
PizzaBuilder pizzaBuilder = new PizzaBuilder.Builder()
.name("불고기피자")
.dough("thin")
.sauce("핫소스")
.topping("페퍼로니")
.price(20000)
.build();
}
값을 세팅 후 자기 자신을 리턴하여 price, dough, 메서드를 계속 호출할 수 있고, 모든 세팅이 끝나면 마지막에 build()를 호출
장점
-> 가독성을 높여주고, 어디에 어떤 값을 세팅해야 하는지 명확하게 알고 사용할 수 있음.
-> 필요한 값만 설정 하면, 나머지 값은, null로 설정 가능
-> 하나의 메서드를 사용하는 것처럼 사용가능하고, 마지막에 build()메서드까지 호출해야지만 객체로써 활용할 수 있으므로 일관성을 지킬 수 있음.
-> 객체 생성이 되는 순간 setting이 되기 때문에 변경 불가능한 객체 만들 수 있음
-> 객체 클래스 내부에 복잡하게 여러 개의 생성자를 만들 필요가 없음
-> build() 함수로 잘못된 값이 세팅되어있는지 검증 가능
단점
-> 빌더 인터페이스의 변화는 이를 구현한 모든 클래스에 영향을 미침
-> 클래스 작성으로 인해 코드가 길어짐
.. 등
빌더 패턴을 적용할 객체에 @Builder 어노테이션을 달기만 하면됨
@Builder
public class Bag {
private String name;
private int money;
private STring memo;
}
생성자에서 사용하게 되면 지정한 값만 빌더 메서드를 사용 가능하게 함
public Pizza(String name, int price) {
this.name = name;
this.price = price;
}
Pizza pizza = pizza.builder()
.name(name)
.price(price)
.build();