home

빌더 패턴

  • 복잡한 객체를 단계별로 생성할 수 있도록 하는 패턴
  • 집을 만드는 것을 생각해보자. 어떤 집은 창문이 있고, 문이 있다. 또한 문과 창문의 종류도 다르다.
  • 간단하게 집을 생성할 때 모든 매개 변수를 포함한 생성자를 만들면 쓰이지 않는 매개변수가 너무 많아진다.

alt text

출처: refactoring.guru

  • 해결방법
    • 빌더 클래스에 복잡한 객체들을 단계별로 생성한다.
    • 모든 단계를 호출할 필요가 없기에, 특정 객체를 제작하는데 필요한 단계만 호출한다.
  • 디렉터라는 별도 관리자 클래스를 둬서 제작 단계의 실행 순서를 정의할 수 있다.

alt text

  • 커스텀 자동차 = 클라이언트가 직접 빌더 사용
  • 인기 자동차 = 디렉터에게 빌더 사용을 위임
  • 사용설명서는 기본 제작 프로세스와 인터페이스를 공유한다. 즉 부품을 제작 하는 것이 아니라 설명 을 한다.

언제 써야하는가?

  • 선택적으로 넣는 매개변수가 많을 때
  • 세부 사항만 다른(ex: 스포츠카, SUV) 유사한 단계를 포함할 때
  • 모든 제품이 인터페이스에 따르는 경우만 디렉터 사용 가능
  • 비지니스 로직에서 복잡한 생성 코드를 분리시킬 수 있다. = 단일 책임의 원칙

추상 팩토리와의 관계

  • 빌더: 복잡한 객체를 단계별로 생성하는데 중점
  • 추상 팩토리: 관련된 객체들의 패밀리를 생성하는데 중점

go 예시 코드

package main
import "fmt"

type Car struct {
	Seats int
	Engine string
	TripComputer bool
	GPS bool
}

type Manual struct {
	Seats string
	Engine string
	TripComputer string
	GPS string
}

type Builder interface {
	Reset()
	SetSeats(seats int)
	SetEngine(engine string)
	SetTripComputer(tripComputer bool)
	SetGPS(gps bool)
}

type CarBuilder struct {
	car *Car
}

func NewCarBuilder() *CarBuilder {
	cb := &CarBuilder{}
	cb.Reset()
	return cb
}

func (cb *CarBuilder) Reset() {
	cb.car = &Car{}
}

func (cb *CarBuilder) SetSeats(seats int) {
	cb.car.Seats = seats
}

func (cb *CarBuilder) SetEngine(engine string) {
	cb.car.Engine = engine
}

func (cb *CarBuilder) SetTripComputer(tripComputer bool) {
	cb.car.TripComputer = tripComputer
}

func (cb *CarBuilder) SetGPS(gps bool) {
	cb.car.GPS = gps
}

func (cb *CarBuilder) GetProduct() *Car {
	product := cb.car
	cb.Reset()
	return product
}

type CarManualBuilder struct {
	manual *Manual
}

func NewCarManualBuilder() *CarManualBuilder {
	mb := &CarManualBuilder{}
	mb.Reset()
	return mb
}

func (mb *CarManualBuilder) Reset() {
	mb.manual = &Manual{}
}

func (mb *CarManualBuilder) SetSeats(seats int) {
	mb.manual.Seats = fmt.Sprintf("이 차에는 %d개의 시트가 있습니다.", seats)
}

func (mb *CarManualBuilder) SetEngine(engine string) {
	mb.manual.Engine = fmt.Sprintf("이 차의 엔진은 %s 입니다.", engine)
}

func (mb *CarManualBuilder) SetTripComputer(tripComputer bool) {
	if tripComputer {
		mb.manual.TripComputer = "트팁 컴퓨터가 존재합니다."
	} else {
		mb.manual.TripComputer = "트립 컴퓨터가 없습니다."
	}
}

func (mb *CarManualBuilder) SetGPS(gps bool) {
	if gps {
		mb.manual.GPS = "해당 차에는 GPS 기능이 있습니다."
	} else {
		mb.manual.GPS = "GPS 기능이 탑재되어있지 않습니다."
	}
}

func (mb *CarManualBuilder) GetProduct() *Manual {
	product := mb.manual
	mb.Reset()
	return product
}

type Director struct{}

func (d Director) ConstructSportsCar(builder Builder) {
	builder.Reset()
	builder.SetSeats(2)
	builder.SetEngine("SprotsEngine")
	builder.SetTripComputer(true)
	builder.SetGPS(true)
}

func (d Director) ConstructSUV(builder Builder) {
	builder.Reset()
	builder.SetSeats(5)
	builder.SetEngine("SUVEngine")
	builder.SetTripComputer(true)
	builder.SetGPS(true)
}

type Application struct {}

func (app *Application) MakeCar() {
	director := &Director{}

	carBuilder := &CarBuilder{}
	director.ConstructSportsCar(carBuilder)
	car := carBuilder.GetProduct()
	fmt.Printf("Car built:\n %+v\n", car)

    manualBuilder := &CarManualBuilder{}
    director.ConstructSportsCar(manualBuilder)
    manual := manualBuilder.GetProduct()
    fmt.Printf("Car manual built:\n %+v\n", manual)
} 

func main() {
    app := &Application{}
    app.MakeCar()
}