design pattern #3 Creational pattern - Builder

  • 앞의 두  factory pattern 은 products 로부터 공통의 interface 를 뽑았다면, builder 는 서로 연관이 없는 product 지만 동일한 construction process 를 사용하는 경우이다.
  • configuration option을 chain 형태로 붙이고 붙인 뒤 build() 하면 결과물이 나오는 구조이다
  • 여기서 특이한점은, 내가 본 builder 는 class 형태로,  build() 할때까지 자기 자신 계속 return 하면서 config 설정하는거였는데 ,여기선 builder 도 interface 로 빠져있다. 
  • 동일한 기능을 inteface 로 묶고, 각기 다른 product 의 config 값 세팅은 director 에서 한다.
  • config 별로 다른 subclass 만들기엔 너무 많은 subclass 가 생기고,
  • constructor param 으로 넣기엔 너무 많은 optional field 들 때문에 null,null,null,... 들이 생길 때 유용하다.

  • 동일한 기능을 갖는 차와 비행기를 각 build  한다고 하면, 차와 비행기를 만드는데 정말 수많은 속성값들이 들어가는데, 이를 위한 assembly code 를  builder class 로 따로 빼고, 차와 비행기가 공유하는 속성을 interface 로 묶을 것이다. 
  • 물론 직접 builder 를 사용해서 원하는 속성값을 하나씩 setting 해서 special oder 혹은 한정판 모델처럼 만들 수도 있지만, 재사용성이 높고, 반복될 수 있다면 director 에서 묶어서 관리하는것이 좋다. 
공통의 config 값인 engine 과 transmission  을 interface builder 로 묶고,

public interface Builder {
void setEngine(Engine engine);
void setTransmission(Transmission transmission);
}


각각의 구체적인 Builder 를 구현한다.

public class CarBuilder implements Builder {
private Engine engine;
private Transmission transmission;

@Override
public void setEngine(Engine engine) {
this.engine = engine;
}

@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}

public Car getResult() {
return new Car(engine, transmission);
}
}

public class AirplaneBuilder implements Builder{
private Engine engine;
private Transmission transmission;

@Override
public void setEngine(Engine engine) {
this.engine = engine;
}

@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}

public Airplane getResult() {
return new Airplane(engine, transmission);
}
}
product class 는 아래처럼 config 들을 가진다.
public class Car {
private final Engine engine;
private final Transmission transmission;
private double fuel = 0;

public Car(Engine engine, Transmission transmission) {
//Constructor
}
//Getter, Setter
}

public class Airplane {
private final Engine engine;
private final Transmission transmission;

public Airplane(Engine engine, Transmission transmission) {
//Constructor
}
}

아래처럼 각각의 config 역시 기능을 가질수도, 단순  enum  일수도 있다.


public class Engine {
private final double volume;
private double mileage;
private boolean started;

public Engine(double volume, double mileage) {
this.volume = volume;
this.mileage = mileage;
}

public void on() {
started = true;
}

public void off() {
started = false;
}

public boolean isStarted() {
return started;
}

public void go(double mileage) {
if (started) {
this.mileage += mileage;
} else {
System.err.println("Cannot go(), you must start engine first!");
}
}

public double getVolume() {
return volume;
}

public double getMileage() {
return mileage;
}
}

public enum Transmission {
SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC
}

실제 builder 에 구체적인 값을 넣는건 director 에서 한다. 
director 는 각각의 config 에 대한 설정값만 알뿐, 뭘 build 하는지는 모른다.
물론 director 없이, builder 만을 가지고 직접 원하는 값들만 세팅 할 수도 있다.
public class Director {

public void constructSportsCar(Builder builder) {
builder.setEngine(new Engine(3.0, 0));
builder.setTransmission(Transmission.SEMI_AUTOMATIC);
}

public void constructSUV(Builder builder) {
builder.setEngine(new Engine(2.5, 0));
builder.setTransmission(Transmission.MANUAL);
}

public void constructAirbus(Builder builder) {
builder.setEngine(new Engine(5000, 0));
builder.setTransmission(Transmission.AUTOMATIC);
}

public void constructBoeing(Builder builder) {
builder.setEngine(new Engine(3000, 100));
builder.setTransmission(Transmission.AUTOMATIC);
}
}

client 에서는 아래와 같이 쓸 수 있다. director 에, 필요한 구체적 builder 를 넣어주면,director 가 값을 채워준다. 그럼 builder 로부터 객체를 꺼내올 수 있다.

public static void main(String[] args) {
Director director = new Director();

// Director gets the concrete builder object from the client
// (application code). That's because application knows better which
// builder to use to get a specific product.
CarBuilder builder = new CarBuilder();
director.constructSportsCar(builder);

// The final product is often retrieved from a builder object, since
// Director is not aware and not dependent on concrete builders and
// products.
Car tesla = builder.getResult();
System.out.println("Car built:\n" + tesla.getEngine());


AirplaneBuilder airplaneBuilder = new AirplaneBuilder();

// Director may know several building recipes.
director.constructAirbus(airplaneBuilder);
Airplane a380 = airplaneBuilder.getResult();
System.out.println("\nAirbus built:\n" + a380.getTranssmission());
    director.constructBoeing(airplaneBuilder);
Airplane boeing797 = airplaneBuilder.getResult();
System.out.println("\nBoeing built:\n" + boeing797.getTranssmission());

}
 

Comments

Popular posts from this blog

삼성전자 무선사업부 퇴사 후기

코드리뷰에 대하여

개발자 커리어로 해외 취업, 독일 이직 프로세스