U E D R , A S I H C RSS

니젤프림/Builder Pattern

1. Builder Pattern


빌더 패턴은 소프트웨어 디자인 패턴의 하나이다. 빌더 패턴은 복잡한 객체를 생성하는 방법과, 표현하는 방법을 정의하는 클래스를 별도로 분리한다. 이 분리로서 얻어지는 효과는, 동일한 생성 과정이 다른 표현을 얻어낼 수 있게 되는 것이다.

쉽게 말해서, 아주 복잡한 오브젝트를 생성해야하는데, 그 일을 오브젝트를 원하는 클래스가 하는게 아니라, Builder 에게 시키는 것이다. 그런데 자꾸 나오는 생성/표현 의 의미는, 바로 director 의 존재를 설명해 준다고 할 수 있다. director 는 Building step(construction process) 을 정의하고 concrete builder 는 product 의 구체적인 표현(representation) 을 정의하기에.. 그리고, builder 가 추상적인 인터페이스를 제공하므로 director 는 그것을 이용하는 것이다.

1.1. 언제 쓰나?

복잡한 데이터구조를 생성해야 할 때. 너무 간단한가?

1.2. 실생활에 적용되는 쉬운 예

패스트(정크)푸드 레스토랑 맥도날드에서 어린이용 해피밀을 만들어내는 걸로 예를 들 수 있다. 일반적으로 해피밀은 메인, 사이드, 음료, 장난감 (햄버거, 프라이, 콜라, 매달 바뀌는 장난감)으로 이루어져 있다. 여기서 중요한건, 이런 템플릿이 정해져 있다는 것이다. 요즘 같이 까다로운 아이들에게 어릴때부터 맥도날드의 입맛을 확실히 들여놓으려면 당연히 다양한 바리에이션이 필요하다. 고객은 햄버거나 치즈버거나, 아니면 맥너겟이나 이런걸 선택할 수 있지만, 기본적으로 해피밀이 구성되는 방식에는 변함 없다. 여기서 빌더 패턴을 적용한다면, 카운터에서 주문을 받는 직원을 Director 라고 할 수 있다. 물론 고객은 Customer 이다. 고객이 원하는 바리에이션을 선택해서 해피밀 셋트를 구성하게 되면 (Customer가 Concrete Builder 를 선택한다) Director 는 정해진 템플릿에 따라 주방 직원(Concrete Builder) 에게 의뢰하여 해피밀 세트(Product) 를 만들어 낸다. 여기서 Director 가 Concrete Builder 에게 요구하는 방식은 종류에 따라 비슷 하므로 그것을 추상화시킨 인터페이스를 Builder 라고 할 수 있겠다.



1.3. Class Diagram

1.3.1. Builder

Product 를 생성하는 템플릿. Abstract Interface 라고도 할 수 있다.

1.3.2. Concrete Builder

Builder 를 구현한 부분. 일반적으로 다수개의 Concrete Builder 가 존재하며, Builder 가 제공하는 인터페이스를 이용해서 late binding 의 형식으로 사용하게 된다. 물론, builder pattern 에서의 주 관심하는 Product 를 만들어내는 것이다.

1.3.3. Director

Director 클래스는 Product 를 생성하는 방법과 순서 등을 주관한다. Builder 인스턴스를 가지고 있기 때문에 Concrete Builder 를 건네 받아서 Builder 에 연결해 준다.

1.3.4. Product

만들어야 할 대상. 일반적으로 Builder Pattern 이 생성하는 Product 는 Composite Pattern 으로 이루어진다.

1.4. 예제 코드

1.4.1. Wikipedia 의 Java code

/** "Product" */
class Pizza {
  private String dough = "";
  private String sauce = "";
  private String topping = "";

  public void setDough (String dough)     { this.dough = dough; }
  public void setSauce (String sauce)     { this.sauce = sauce; }
  public void setTopping (String topping) { this.topping = topping; }
}


/** "Abstract Builder" */
abstract class PizzaBuilder {
  protected Pizza pizza;

  public Pizza getPizza() { return pizza; }
  public void createNewPizzaProduct() { pizza = new Pizza(); }

  public abstract void buildDough();
  public abstract void buildSauce();
  public abstract void buildTopping();
}

/** "ConcreteBuilder" */
class HawaiianPizzaBuilder extends PizzaBuilder {
  public void buildDough()   { pizza.setDough("cross"); }
  public void buildSauce()   { pizza.setSauce("mild"); }
  public void buildTopping() { pizza.setTopping("ham+pineapple"); }
}

/** "ConcreteBuilder" */
class SpicyPizzaBuilder extends PizzaBuilder {
  public void buildDough()   { pizza.setDough("pan baked"); }
  public void buildSauce()   { pizza.setSauce("hot"); }
  public void buildTopping() { pizza.setTopping("pepperoni+salami"); }
}


/** "Director" */
class Waiter {
  private PizzaBuilder pizzaBuilder;

  public void setPizzaBuilder (PizzaBuilder pb) { pizzaBuilder = pb; }
  public Pizza getPizza() { return pizzaBuilder.getPizza(); }

  public void constructPizza() {
    pizzaBuilder.createNewPizzaProduct();
    pizzaBuilder.buildDough();
    pizzaBuilder.buildSauce();
    pizzaBuilder.buildTopping();
  }
}

/** A customer ordering a pizza. */
class BuilderExample {
  public static void main(String[] args) {
    Waiter waiter = new Waiter();
    PizzaBuilder hawaiianPizzaBuilder = new HawaiianPizzaBuilder();
    PizzaBuilder spicyPizzaBuilder = new SpicyPizzaBuilder();

    waiter.setPizzaBuilder ( hawaiianPizzaBuilder );
    waiter.constructPizza();

    Pizza pizza = waiter.getPizza();
  }
}

1.4.2. Composite Pattern 을 생성해주는 Builder Pattern 예제 코드

Head First Design Pattern 에 나오는, Vacation Planner 를 구현한 코드
1.4.2.1. nijel.product
// PlanComponent
package nijel.product;

public class PlanComponent {	
	protected String name;
	protected String description;

	public PlanComponent(String name, String description) {
		this.name = name;
		this.description = description;
	}
	
	public void add(PlanComponent planComponent) {
		throw new UnsupportedOperationException();
	}
	
	public void remove(PlanComponent planComponent) {
		throw new UnsupportedOperationException();
	}
	public PlanComponent getChild(int index) {
		throw new UnsupportedOperationException();
	}
	
	public String getName() {
		throw new UnsupportedOperationException();
	}
	
	public String getDescription() {
		throw new UnsupportedOperationException();
	}
	
	public void print() {
		throw new UnsupportedOperationException();
	}
}

// Plan
package nijel.product;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Plan extends PlanComponent {
	private List<PlanComponent> planComponents;

	public String getName() {
		return name;
	}
	
	public String getDescription() {
		return description;
	}
	
	public Plan(String name, String description) {
		super(name, description);
		
		planComponents = new ArrayList<PlanComponent>();
	}

	public void add(PlanComponent planComponent) {
		planComponents.add(planComponent);
	}

	public PlanComponent getChild(int index) {
		return planComponents.get(index);
	}

	public void remove(PlanComponent planComponent) {
		planComponents.remove(planComponent);
	}
	
	public void print() {
		System.out.println("\n" + getName());
		System.out.println(", " + getDescription());
		System.out.println("------------------------------");
		
		Iterator iterator = planComponents.iterator();
		while (iterator.hasNext()) {
			PlanComponent planComponent = (PlanComponent) iterator.next();
			planComponent.print();
		}
	}
}

// PlanItem
package nijel.product;

public class PlanItem extends PlanComponent {
	
	public PlanItem(String name, String description) {
		super(name, description);
	}
	
	public String getName() {
		return name;
	}
	
	public String getDescription() {
		return description;
	}
	
	public void print() {
		System.out.println("\t" + getName() + "\n");
		System.out.println("\t\t" + getDescription() + "\n");
	}
}


1.4.2.2. nijel.builder
// director
package nijel.builder;

import nijel.product.PlanComponent;

public abstract class Planner {
	abstract public PlanComponent makePlan();
}

// concrete director
package nijel.builder;

import nijel.product.PlanComponent;

public class MyPlanner extends Planner {
	
	private Builder builder;

	public MyPlanner(Builder builder) {
		this.builder = builder;
	}
	
	public PlanComponent makePlan() {
		int first, second, third;
		
		first = 1;
		builder.buildDay(first);
		
		builder.addHotel(first, "Patternsland 5 star Hotel");
		builder.addTickets(first, "Park Ticket: R");
		builder.addReservation(first, "Dinner at VERY expensive French restaurant");
		
		second = 2;
		builder.buildDay(second);
		
		builder.addHotel(second, "Maybe Hotel");
		builder.addTickets(second, "Park Ticket: A");
		builder.addSpecialEvent(second, "Patterns on Ice");
		builder.addReservation(second, "just dinner");
		
		third = 3;
		builder.buildDay(third);
		builder.addHotel(third, "Wannabe Hotel");
		builder.addTickets(third, "Ticket for Children!");
		
		return builder.getVacationPlan();
	}
}

// builder
package nijel.builder;

import nijel.product.PlanComponent;

public interface Builder {
	abstract public void buildDay(int date);
	abstract public void addHotel(int date, String hotel);
	abstract public void addTickets(int date, String ticket);
	abstract public void addSpecialEvent(int date, String event);
	abstract public void addReservation(int date, String restaurant);
	
	abstract public PlanComponent getVacationPlan();
}

// concrete builder
package nijel.builder;

import nijel.product.Plan;
import nijel.product.PlanComponent;
import nijel.product.PlanItem;

public class VacationBuilder implements Builder {
	private PlanComponent plan;
	
	public VacationBuilder(String description) {
		plan = new Plan(description);
	}
	
	public void addHotel(int date, String hotel) {
		PlanComponent hotelReservation = new PlanItem(hotel);
		
		plan.getChild(date - 1).add(hotelReservation);
	}

	public void addSpecialEvent(int date, String event) {
		PlanComponent specialEvent = new Plan("Special Event");
		specialEvent.add(new PlanItem(event));
		
		plan.getChild(date - 1).add(specialEvent);
	}

	public void addTickets(int date, String ticket) {
		PlanComponent admissionTicket = new PlanItem(ticket);
		
		plan.getChild(date - 1).add(admissionTicket);
	}

	public void buildDay(int date) {
		plan.add(new Plan(date + "day's plan"));
	}

	public PlanComponent getVacationPlan() {
		return plan;
	}

	public void addReservation(int date, String restaurant) {
		PlanComponent reservation = new Plan("Dining");
		reservation.add(new PlanItem(restaurant));
		
		plan.getChild(date - 1).add(reservation);
		
	}
}




1.4.2.3. nijel.client
package nijel.client;

import nijel.builder.*;
import nijel.product.*;

public class Client {
	public static void main(String[] args) {
		PlanComponent perfectVacationPlan;
		Planner perfectPlanner =
					new MyPlanner(new VacationBuilder("Perfect Vacation Plan"));
		
		perfectVacationPlan = perfectPlanner.makePlan();
		
		perfectVacationPlan.print();
	}
}

1.5. Builder Pattern 과 Abstract Factory Pattern 의 차이점

보시다 시피 Builder Pattern 과 Abstract Factory Pattern 은 많이 비슷하다. 차이점이라면 약간 미묘하다고도 할 수 있는데, Abstract Factory Pattern 은 무엇이 만들어지는가 에 초점을 맞추고 있고, Builder Pattern 은 어떻게 만들어 지는가에 초점을 맞추고 있다고 풀이할 수 있다. 물론 두 종류의 Creational Pattern 은 Product 을 제공하는데 첫번째 책임이 존재한다. 가장 큰 차이점이라면, Builder Pattern 은 복잡한 오브젝트를 한단계 한단계 만들어서 최종 단계에 리턴해주고, Abstract Factory Pattern 은 여러 종류의 generic 한 product 를 여러종류의 concrete factory 가 생성하기 때문에 각각의 오브젝트가 만들어질 때마다 product 를 받을 수 있게 된다.

Abstract Factory Pattern : 어떤 종류의 명확한 오브젝트를 생성할 것인지를 실행 시간까지 연기한다. (실행시간에 결정한다) 예를 들자면, 레스토랑에서 오늘의 추천메뉴를 선택하는 것이다. 서버(interface to factory)는 "오늘의 추천요리" 라는 abstract generic message 를 주방(concrete factory)에 전달하고 음식을 받게 될 때 concrete product 를 얻게 되는 것이다.

Builder Pattern : 복잡한 오브젝트를 어떤 방식으로 조합하여 만들어낼지 에 대한 방법을 제공한다. (encapsulate) 예를 들자면, 건축업자와 계약을 해서 집을 건설 할 때, 건축업자(builder) 에게 평면도(product 의 윤곽)를 제공하면 건축업자는 일련의 작업을 거쳐서 집(complex object)을 만들어 낸다.

업데이트 예정.
원문은 http://en.wikipedia.org/wiki/Builder_pattern
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:28:55
Processing time 0.0258 sec