DEV Community

Cover image for [ Design Patterns ] - Builder pattern with Golang
Tuan Nguyen Van
Tuan Nguyen Van

Posted on

[ Design Patterns ] - Builder pattern with Golang

Ha Noi, on Thursday 10/02/2023

At the moment, a little speech to give to the present...

Hanoi, Viet Nam

Hà Nội mùa này vắng những cơn mưa

Cái rét đầu đông khăn em hiu hiu gió lạnh

Hoa sữa thôi rơi ta bên nhau một chiều tan lớp

Đường Cổ Ngư xưa chầm chậm bước ta về...😥😥😥

Ha Noi is approaching winter, when there will be no rain. I'm sitting in my room and writing this post. Today's temperature in Ha Noi is 25°C, cloudy, and starting to get colder than previous days. The weather is appropriate for lonely people 😉😉😉

The news about layoffs in big tech is not good for everyone around the world, and it was beginning to affect employees in Vietnam. I hope that by 2023, everything will be back to normal. Best wishes to everyone! 😍😍😍

I. Builder pattern

The Builder pattern is used when the desired product is complex and requires multiple steps to complete. In this case, several construction methods would be simpler than a single monstrous constructor. The potential problem with the multistage building process is that a partially built and unstable product may be exposed to the client. The Builder pattern keeps the product private until it’s fully built.

Reference : Builder pattern

II. Components of the builder pattern

builder pattern

  1. Product : These are the resultant objects. Products will have different results. It will be determined by how the object is constructed.

  2. Concreate Builder : Provides implementation for Builder. It is an object able to construct other objects. Constructs and assembles parts to build the objects.

  3. Builder : Abstract interface for creating objects (product) and getting concreate builder.

  4. Director : The class will call order-by-step to build objects.

III. Implement

1. The problem

My team must build one application for selling offline fruits to one restaurant. The customer will select fruits and add them to their cart after going to the counter and requiring payment. The employee of the restaurant will scan the QR code of the fruit, and your application will display information about the fruit's price, amount, and discount.

The application must calculate the amount of money that the customer must pay for the restaurant according to the rules below.

Example rules :

Name Amount Price Discount
Orange More than 10 Price = Price - $5
eg : each fruit's price = $49.99 => Price = $49.99 - $5 = 44.99$ Discount = Discount + 2%
eg : each fruit's discount = 10% => Discount = 10 + 2 = 12%
Orange Less than 10 fruit's price fruit's discount
Apple More than 10 Price = Price - $10
eg : each fruit's price = $49.99 => Price = $49.99 - $10 = 39.99$ Discount = Discount + 5%
eg : each fruit's discount = 10% => Discount = 10 + 5 = 15%
Apple Less than 10 fruit's price fruit's discount

Note : *The restaurant will have far more fruit, and the rules will be far more complicated. *

2. Problem solving

I think you will code as in the example below. We will use if-else to check the fruit's name and process logic for it in each case.

type Output struct {
    Money float64
}

func GetMoney(name string, amount int64, price, discount float64) Output {
    money := float64(0)
    if name == "orange" {
        if amount > 10 {
            price -= 5
            discount += 2
            money = price * (discount - discount/100) * float64(amount)
        }
        // do anything logical
    } else if name == "apple" {
        if amount > 10 {
            price -= 10
            discount += 5
            money = price * (discount - discount/100) * float64(amount)
        }
        // do anything logical
    }

    return Output{Money: money}
}
Enter fullscreen mode Exit fullscreen mode

Sure, it will still work, but when restaurants need to calculate the price, discount, and scale the sale of fruit, our codebase becomes too large, and updating the codebase becomes difficult.

When the restaurant adds one new fruit, updates, or adds one new rule, we must hold a meeting to confirm and update the codebase in a unified manner.

So, to resolve the problem, we will use the builder design pattern.

3. Implement builder design pattern

3.1. ifruit_builder.go : Builder interface

package builder

import (
    "design-pattern-golang-example/builder-pattern/concreate_builder"
    "design-pattern-golang-example/builder-pattern/constant"
    "design-pattern-golang-example/builder-pattern/product"
)

type IFruitBuilder interface {
    SetPrice(in *product.InfoInput)
    SetDiscount(in *product.InfoInput)
    SetMoneyPayment(in *product.InfoInput)
    ToOutputMoney() *product.InfoOutput
}

func GetFruitBuilder(fruitName string) IFruitBuilder {
    switch fruitName {
    case constant.Orange:
        return concreate_builder.NewOrangeBuilder()
    case constant.Apple:
        return concreate_builder.NewAppleBuilder()
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

3.2. orange_builder.go: Concreate builder for orange

package concreate_builder

import "design-pattern-golang-example/builder-pattern/product"

type OrangeBuilder struct {
    Price        float64
    Discount     float64
    MoneyPayment float64
}

func NewOrangeBuilder() *OrangeBuilder {
    return &OrangeBuilder{}
}

func (o *OrangeBuilder) SetPrice(in *product.InfoInput) {
    if in.Amount > 10 {
        o.Price = in.Price - 5
    }
}

func (o *OrangeBuilder) SetDiscount(in *product.InfoInput) {
    if in.Amount > 10 {
        o.Discount = in.Discount + 2
    }
}

func (o *OrangeBuilder) SetMoneyPayment(in *product.InfoInput) {
    o.MoneyPayment = o.Price * (100 - o.Discount/100) * float64(in.Amount)
}

func (o *OrangeBuilder) ToOutputMoney() *product.InfoOutput {
    return &product.InfoOutput{
        MoneyPayment: o.MoneyPayment,
    }
}
Enter fullscreen mode Exit fullscreen mode

3.3. apple_builder.go: Concreate builder for apple

package concreate_builder

import "design-pattern-golang-example/builder-pattern/product"

type AppleBuilder struct {
    Price        float64
    Discount     float64
    MoneyPayment float64
}

func NewAppleBuilder() *AppleBuilder {
    return &AppleBuilder{}
}

func (o *AppleBuilder) SetPrice(in *product.InfoInput) {
    if in.Amount > 10 {
        o.Price = in.Price - 10
    }
}

func (o *AppleBuilder) SetDiscount(in *product.InfoInput) {
    if in.Amount > 10 {
        o.Discount = in.Discount + 5
    }
}

func (o *AppleBuilder) SetMoneyPayment(in *product.InfoInput) {
    o.MoneyPayment = o.Price * (100 - o.Discount/100) * float64(in.Amount)
}

func (o *AppleBuilder) ToOutputMoney() *product.InfoOutput {
    return &product.InfoOutput{
        MoneyPayment: o.MoneyPayment,
    }
}
Enter fullscreen mode Exit fullscreen mode

3.4. fruit.go : Product

package product

type InfoInput struct {
    Price    float64
    Discount float64
    Amount   int64
}

type InfoOutput struct {
    MoneyPayment float64
}
Enter fullscreen mode Exit fullscreen mode

3.5. director.go: Director

package director

import (
    "design-pattern-golang-example/builder-pattern/builder"
    "design-pattern-golang-example/builder-pattern/product"
)

type Director struct {
    FruitBuilder builder.IFruitBuilder
}

func NewDirector(f builder.IFruitBuilder) *Director {
    return &Director{
        FruitBuilder: f,
    }
}

func (d *Director) BuildOutput(in *product.InfoInput) *product.InfoOutput {
    d.FruitBuilder.SetPrice(in)
    d.FruitBuilder.SetDiscount(in)
    d.FruitBuilder.SetMoneyPayment(in)
    return d.FruitBuilder.ToOutputMoney()
}
Enter fullscreen mode Exit fullscreen mode

3.6. main.go

package main

import (
    "fmt"

    "design-pattern-golang-example/builder-pattern/builder"
    "design-pattern-golang-example/builder-pattern/constant"
    "design-pattern-golang-example/builder-pattern/director"
    "design-pattern-golang-example/builder-pattern/product"
)

func main() {
    in := &product.InfoInput{
        Price:    100,
        Discount: 10,
        Amount:   15,
    }

    builderApple := builder.GetFruitBuilder(constant.Apple)
    directorApple := director.NewDirector(builderApple)
    outApple := directorApple.BuildOutput(in)

    builderOrange := builder.GetFruitBuilder(constant.Orange)
    directorOrange := director.NewDirector(builderOrange)
    outOrange := directorOrange.BuildOutput(in)

    fmt.Printf("Pay for orange : $%v\n", outOrange.MoneyPayment)
    fmt.Printf("Pay for apple : $%v\n", outApple.MoneyPayment)
}
Enter fullscreen mode Exit fullscreen mode

Ouput :

Pay for orange : $142329
Pay for apple : $134797.5

Process finished with the exit code 0
Enter fullscreen mode Exit fullscreen mode

IV. Source code

Top comments (0)