빌더 패턴은 객체 인스턴스를 생성해주는 대표적인 패턴으로 spring에서 lombok을 사용했다면 디자인 구조를 몰라도 잘 사용해왔을 것이다.
그 패턴을 직접 구현하는 구조를 짜보자!
public class House {
private String elevator;
private String window;
private String park;
private int room;
private Boolean parking;
public House() {
}
public House(String elevator, String window, String park, int room, Boolean parking) {
this.elevator = elevator;
this.window = window;
this.park = park;
this.room = room;
this.parking = parking;
}
public void setElevator(String elevator) {
this.elevator = elevator;
}
public void setWindow(String window) {
this.window = window;
}
public void setPark(String park) {
this.park = park;
}
public void setRoom(int room) {
this.room = room;
}
public void setParking(Boolean parking) {
this.parking = parking;
}
@Override
public String toString() {
return "House{" +
"elevator='" + elevator + '\'' +
", window='" + window + '\'' +
", park='" + park + '\'' +
", room=" + room +
", parking=" + parking +
'}';
}
}
집을 짓기 위해 class를 정의했다. 해당 class를 통해 인스턴스를 생성하려면
public class Client {
public static void main(String[] args) {
House apartment = new House();
apartment.setElevator("현대");
apartment.setWindow("고급 창문");
apartment.setPark("호수 공원");
apartment.setParking(true);
apartment.setRoom(500);
House building = new House();
building.setParking(false);
building.setRoom(10);
building.setWindow("일반 창문");
System.out.println(apartment.toString());
System.out.println(building.toString());
}
}
다음과 같은 set 을 반복하는 코드를 작성하거나 길어진 생성자에 직접 매개변수를 일일히 넣어주어 생성해야한다.
이제 이를 builder 패턴을 적용시켜보자!
public interface HouseBuilder {
HouseBuilder elevator(String elevator);
HouseBuilder window(String window);
HouseBuilder park(String park);
HouseBuilder room(int room);
HouseBuilder parking(Boolean parking);
House build();
}
기본 뼈대가 될 인터페이스를 작성했다. 여기서 주목할 점은 chaning을 위해 반환하는 객체 타입이 HouseBuilder
를 반환하므로써 계속해서 체이닝이 가능하다는 것이다. 이 말이 이해가 안되면 좀있다 코드로 확인해보자.
public class DefaultHouseBuilder implements HouseBuilder{
private String elevator;
private String window;
private String park;
private int room;
private Boolean parking;
@Override
public HouseBuilder elevator(String elevator) {
this.elevator = elevator;
return this;
}
@Override
public HouseBuilder window(String window) {
this.window = window;
return this;
}
@Override
public HouseBuilder park(String park) {
this.park = park;
return this;
}
@Override
public HouseBuilder room(int room) {
this.room = room;
return this;
}
@Override
public HouseBuilder parking(Boolean parking) {
this.parking = parking;
return this;
}
@Override
public House build() {
return new House(elevator, window, park, room, parking);
}
}
public class Client {
public static void main(String[] args) {
HouseBuilder apartBuilder = new DefaultHouseBuilder();
House apartment = apartBuilder.elevator("현대")
.window("고급 창문")
.park("숲 공원")
.room(500)
.parking(true)
.build();
HouseBuilder buildingBuilder = new DefaultHouseBuilder();
House building = buildingBuilder.window("일반 창문")
.room(10)
.parking(false)
.build();
System.out.println(apartment.toString());
System.out.println(building.toString());
}
}
위의 결과와 동일한 결과를 얻을 수 있는데. 위에서 set을 반복하던 코드에 비해 더 깔끔한 결과를 얻을 수 있었다.
여기서 builder pattern의 또 하나의 장점을 만들 수 있는데. 만약 아파트를 같은 구조로 계속 지어나간다면 계속 builder를 사용할 필요가 없이 지어진 그대로 가져오면 된다.
public class ApartBuilder {
private HouseBuilder houseBuilder;
public ApartBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public House prugio(){
return houseBuilder
.elevator("대우")
.window("고급 창문")
.room(500)
.park("대우 공원")
.parking(true)
.build();
}
public House xi(){
return houseBuilder
.elevator("GS")
.window("고급 창문")
.room(500)
.park("자이 공원")
.parking(true)
.build();
}
}
다음과 같이 아파트 브랜드별로 찍어내고자 한다면
public class Client {
public static void main(String[] args) {
HouseBuilder builder = new DefaultHouseBuilder();
ApartBuilder apartBuilder = new ApartBuilder(builder);
House prugio = apartBuilder.prugio();
House xi = apartBuilder.xi();
System.out.println(prugio.toString());
System.out.println(xi.toString());
}
}
다음과 같은 결과도 확인해볼 수 있다.
복잡한 객체 생성 과정을 숨길 수 있고 더 세부적인 다양한 구조를 작성해볼 수 있다. 또한 지금은 build()
에서 바로 new 객체()를 통해 반환하고 있지만 더 자세한 구현체를 얻기 위해 null 체크를 한다던지 들어올 수 없는 값을 체크한다던지 로직을 추가하여 더욱 단단한 객체를 반환해줄 수도 있다.
단점으로는 builder라는 객체를 생성 해주어야하는 성능적인 부분이 존재하고, 다른 디자인 패턴들에서도 함께 존재하는 구조가 복잡해진다는 단점이 있다.