Builder

We want to build some cars here. Let's see we can use builder pattern build some cars.

Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations.

Define what is a car? It can drive, brake and steer.

package car

type Car interface {
  Drive() error
  Brake() error
  Steer() error
}

What are the parts that make up a car?

type Engine struct {
  PeakTorqueRPM uint
  MaxTorque     uint
}

type Wheels struct {
  Width    float64
  TireMake string
}

Now how should a builder behave? It should install parts.

type Builder interface {
  Init(model string) Builder
  InstallEngine(*Engine) Builder
  InstallWheels(*Wheels) Builder
  Build() Car
}

Here's an example of builder that should satisfy the Builder interface. Let's say that Porsche can only build Boxster at the moment.

type PorscheBuilder struct {
  vehicle *Boxster
}

func (pb *PorscheBuilder) Init(model string) {
  switch model {
  case Cayman:
      pb.vehicle = &Caymen{}
  case Boxster:
      pb.vehicle = &Boxster{}
  default:
      pb.vehicle = &Macan{}
  }
}

func (pb *PorscheBuilder) InstallEngine(e *Engine) Builder {
  pb.vehicle.horspower = float64(e.MaxTorque*e.PeakTorqueRPM) / 5252

  return pb
}

func (pb *PorscheBuilder) InstallWheels(w *Wheels) Builder {
  switch w.TireMake {
  case Michelin:
    pb.car.brakeDist = 16 * w.Width
  default:
    pb.car.brakeDist = 25 * w.Width
  }

  return pb
}

func (pb *PorscheBuilder) Build() Car {
  return pb.vehicle
}

The Boxster struct should look like this.

type Boxster struct {
  horsepower float64
  brakeDist  float64
}

func (b *Boxster) Drive() error {
  fmt.Printf("Accelerating with %f horsepower\n", b.horsepower)
  return nil
}

func (b *Boxster) Brake() error {
  fmt.Printf("Coming to a stop in %f feet\n", b.brakeDist)
  return nil
}

Ideally we can have many different types of builder, like BMWBuilder, AudiBuilder and etc...

func NewBuilder(brand string) (Builder, error) {
  switch brand {
  case Porsche:
    return &PorscheBuilder{}, nil
  case BMW:
    return nil, errors.New("can't build BMW yet")
  case Mazda:
    return nil, errors.New("can't build Mazda yet")
  default:
    return nil, fmt.Errorf("%s is not a recognized brand", brand)
  }
}

Finally we can use what we have developed to build some Boxsters.

func main() {
  builder, err := car.NewBuilder(car.Porsche)
  if err != nil {
    return
  }

  car := builder.
    InstallWheels(&car.Wheels{Width: 7.5, TireMake: Michelin}).
    InstallEngine(&car.engine{MaxTorque: 300, PeakTorqueRPM: 6000}).
    Build()

  car.Drive()
  car.Brake()
}

Last updated