-
[생성 패턴] BuilderCS/디자인패턴 2021. 5. 21. 21:35
Builder Pattern?
Builder(빌더) 패턴이란,
레고 블록을 조립하는 것처럼
객체를 생성하자.레고 블록을 어떻게 조립하는지에 따라 최종 모양이 결정되는 것처럼, 객체도 생성자를 통해서 한 번에 만들지 말고 필요한 부분들만 조립할 수 있게 하자.
왜 이런 패턴이 필요한가를 생각해기 위해 다음의 코드를 살펴보자.
피자의 정보(크기, 치즈 유무, 토핑 종류)를 담고 있는 Pizza 클래스가 있다.
public class Pizza { public Pizza(int size) { ... } public Pizza(int size, boolean cheese) { ... } public Pizza(int size, boolean cheese, boolean pepperoni) { ... } // ... }
Pizza 클래스 입장에서는 속성의 개수에 비례하여 생성자를 갖고 있어야 할 것이다. 또한 Pizza 클래스를 사용하는 입장에서는 Pizza 클래스를 올바르게 생성하기 위해서 인자를 어떤 순서로 넘겨줘야 하는지를 알고 있어야 한다.
속성이 많아지면 많아질수록 생성자는 많아질 것이고, 사용자가 기억해야 하는 내용도 많아진다는 문제가 생긴다.
이러한 상황을 타개할 수 있는 패턴이 바로 Builder Pattern 이다.
Structure
Builder Pattern - Builder: Product의 어떤 부분들이 조립될 수 있는 영역인지를 제공하는 추상 인터페이스
- ConcreteBuilder(또는 SimpleClass): Builder에 의해 분리된 부분을 조립하는 기능을 제공하고, 최종 조립된 Product를 반환하는 클래스
- Director: Builder 인터페이스를 사용해서 인스턴스를 조립하는 역할
- Product: 사용자가 최종 얻고자 하는 인스턴스 (Director에 의해 조립되는 대상)
이 구조는 GoF에서 이야기하는 Builder Pattern의 모습인데, 조슈아 블로흐가 쓴 "Effective Java"를 통해 또 다른 Builder Pattern의 모습을 볼 수 있다.
Example
GoF의 Builder Pattern
Product인 Car 클래스가 아래와 같이 되어있다고 하자.
public class Car { private String carType; private String engine; private String seats; private String fuelType; public Car (String carType) { this.carType = carType; } // getter ... // setter ... @Override public String toString() { return this.carType + "=> { engine:" + this.engine + ", seats: " + this.seats + ", fuelType: " + this.fuelType + " }"; } }
이 Car 클래스는 carType이라는 속성에 따라서 나머지 속성(engine, seats, fuelType)이 정해지는 특징이 있다. 이러한 Car 오브젝트를 어떻게 만들어야 할까.
다음과 같이 Builder 인터페이스를 정의해보자.
public interface CarBuilder { public void buildEngine(); public void buildSeats(); public void buildFuelType(); public Car getCar(); }
CarBuilder는 Car를 생성하는 ConcreteBuilder들이 가져야 하는 공통의 기능을 정의하고 있다. 즉, ConcreteBuilder들은 엔진, 좌석, 연료 타입을 각자에 맞게 설정할 수 있어야 하고, 최종적으로 만들어지는 Car를 반환할 수 있도록 만들어져야 한다는 뜻이다.
이제 ConcretBuilder의 모습을 보면, 아래와 같다.
public class SedanBuilder implements CarBuilder { private Car car = new Car("SEDAN"); @Override public void buildEngine() { car.setEngine("3.5L Duramax V 6 DOHC"); } @Override public void buildSeats() { car.setSeats("Front seat center armrest.Rear seat center armrest.Split-folding rear seats"); } @Override public void buildFuelType() { car.setFuelType("Gasoline 19 MPG city, 29 MPG highway, 23 MPG combined and 437 mi. range"); } @Override public Car getCar() { return car; } }
public class SportsBuilder implements CarBuilder { private Car car = new Car("SPORTS"); @Override public void buildEngine() { car.setEngine("3.6L V 6 DOHC and variable valve timing"); } @Override public void buildSeats() { car.setSeats("Driver sports front seat with one power adjustments manual height, front passenger seat sports front seat with one power adjustments"); } @Override public void buildFuelType() { car.setFuelType("Gasoline 17 MPG city, 28 MPG highway, 20 MPG combined and 380 mi. range"); } @Override public Car getCar() { return car; } }
이 ConcreteBuilder를 통해서 Car를 조립해줄 Director를 만들어보자.
public class CarDirector { private CarBuilder builder; public CarDirector(CarBuilder builder) { this.builder = builder; } public void setBuilder(CarBuilder builder) { this.builder = builder; } public void build() { builder.buildEngine(); builder.buildSeats(); builder.buildFuelType(); } }
Director는 실제 Product의 형태는 모르지만, Builder의 인터페이스를 통해서 Builder가 어떤 조립 부품이 있는지 알 수 있고, 이를 이용해서 조립을 수행할 수 있다.
이제 Builder를 테스트하면 다음과 같은 결과를 얻을 수 있다.
public class testBuilder { public static void main (String[]args) { CarDirector director = new CarDirector(); CarBuilder builder = new SedanBuilder(); director.setBuilder(builder); director.build(); Car sedan = builder.getCar(); System.out.print(sedan); System.out.print("\n"); System.out.print("\n"); builder = new SportsBuilder(); director.setBuilder(builder); director.build(); Car sports = builder.getCar(); System.out.print(sports); } }
SEDAN=> { engine:3.5L Duramax V 6 DOHC, seats: Front seat center armrest.Rear seat center armrest.Split-folding rear seats, fuelType: Gasoline 19 MPG city, 29 MPG highway, 23 MPG combined and 437 mi. range } SPORTS=> { engine:3.6L V 6 DOHC and variable valve timing, seats: Driver sports front seat with one power adjustments manual height, front passenger seat sports front seat with one power adjustments, fuelType: Gasoline 17 MPG city, 28 MPG highway, 20 MPG combined and 380 mi. range }
여기까지가 위에서 보여준 구조(Director, Builder, ConcreteBuilder, Product)를 사용하여 만들었을 때의 모습이다. 사용자는 단순하게 만들고자 하는 Car의 Builder를 선택해서 Director에게 넘겨주면 된다. 실제 Car에 대한 조립은 Director가 알아서 할 것이다.
Effective Java의 Builder Pattern
다른 모습의 Builder 예제를 만들어보자.
이전 코드들을 잠시 내려놓고, 다음의 코드를 살펴보자.
public class Car { private String carType; private String engine; private String seats; private String fuelType; public static class Builder { private String carType; private String engine = "Default Engine"; private String seats = "Default Seats"; private String fuelType = "Default fuelType"; public Builder(String carType) { this.carType = carType; } public Builder engine(String engine) { this.engine = engine; return this; } public Builder seats(String seats) { this.seats = seats; return this; } public Builder fuelType(String fuelType) { this.seats = fuelType; return this; } public Car build() { return new Car(this); } } public Car(Builder builder) { this.carType = builder.carType; this.engine = builder.engine; this.seats = builder.seats; this.fuelType = builder.fuelType; } @Override public String toString() { return this.carType + "=> { engine:" + this.engine + ", seats: " + this.seats + ", fuelType: " + this.fuelType + " }"; } }
이전 설계에서 외부에 존재하던 ConcreteBuilder를 Product 내부로 가져왔다고 생각하면 된다. 대신 Builder 내부에서 return this 를 통해서 " . "을 통한 체인이 가능하게 하였고, Car의 생성자가 Builder를 입력받는 구조로 수정이 되었다.
이 코드는 아래와 같이 사용할 수 있다.
public class TestBuilder2 { public static void main (String[]args) { Car custom = new Car.Builder("CUSTOM") .engine("custom_engine") .seats("custom_seats") .build(); System.out.println(custom); } }
CUSTOM=> { engine:custom_engine, seats: custom_seats, fuelType: Default fuelType }
이 같은 구조로 빌더 패턴을 사용하면 인스턴스를 생성하는 과정을 유연하게 가져갈 수 있다는 장점이 있다.
References
- Rohit Joshi. 2015. "Java Design Pattern". Java Code Geeks
- https://refactoring.guru/design-patterns/builder. accessed 2021.05.21
- https://johngrib.github.io/wiki/builder-pattern/. modified 2020.06.18. accessed 2021.05.21
'CS > 디자인패턴' 카테고리의 다른 글
[구조 패턴] Adapter (0) 2021.05.26 [생성 패턴] Singleton (0) 2021.05.25 [생성 패턴] Prototype (0) 2021.05.20 [생성 패턴] Abstract Factory Method (0) 2021.05.19 [생성 패턴] Factory Method (0) 2021.05.18