Builder Design Pattern in Go

1. Definition

The Builder Design Pattern separates the construction of a complex object from its representation. By doing so, the same construction process can create different representations.

2. Problem Statement

Imagine you're developing software for a fast-food chain. Burgers can have multiple ingredients and variants, like cheese, lettuce, tomato, and so on. Hardcoding every possible combination would be inefficient and inflexible.

3. Solution

Instead of using a monolithic constructor or setters, the Builder pattern suggests offloading the construction steps to separate builder objects. These objects perform the construction steps and return the resulting product.

4. Real-World Use Cases

1. Building complex configurations for a computer system.

2. Constructing meals with different ingredients in fast food chains.

3. Document converters that allow different outputs (e.g., TXT, PDF, DOCX).

5. Implementation Steps

1. Create an interface for building parts of the complex product.

2. Implement concrete builders for specific variants and configurations.

3. Direct the construction process on behalf of the client.

4. Separate the complex object construction from its representation.

6. Implementation in Go

package main
import (
	"fmt"
)
// Step 1: Product to be built
type Burger struct {
	cheese bool
	tomato bool
	lettuce bool
}
func (b Burger) DisplayBurger() {
	fmt.Println("Burger with:")
	if b.cheese {
		fmt.Println("- Cheese")
	}
	if b.tomato {
		fmt.Println("- Tomato")
	}
	if b.lettuce {
		fmt.Println("- Lettuce")
	}
}
// Step 2: Builder interface
type BurgerBuilder interface {
	AddCheese() BurgerBuilder
	AddTomato() BurgerBuilder
	AddLettuce() BurgerBuilder
	Build() Burger
}
// Step 3: Concrete builder
type ConcreteBurgerBuilder struct {
	burger Burger
}
func (b *ConcreteBurgerBuilder) AddCheese() BurgerBuilder {
	b.burger.cheese = true
	return b
}
func (b *ConcreteBurgerBuilder) AddTomato() BurgerBuilder {
	b.burger.tomato = true
	return b
}
func (b *ConcreteBurgerBuilder) AddLettuce() BurgerBuilder {
	b.burger.lettuce = true
	return b
}
func (b *ConcreteBurgerBuilder) Build() Burger {
	return b.burger
}
// Client code
func main() {
	builder := &ConcreteBurgerBuilder{}
	burger := builder.AddCheese().AddTomato().Build()
	burger.DisplayBurger()
}

Output:

Burger with:
- Cheese
- Tomato

Explanation:

1. A Burger struct is defined with several options.

2. The BurgerBuilder interface provides a common platform for building parts of the burger.

3. The ConcreteBurgerBuilder provides concrete implementations for the builder interface, allowing for burger creation with desired configurations.

4. The client code, in main(), demonstrates how to use the builder to construct a specific variant of the burger.

7. When to use?

Use the Builder Pattern when:

1. The algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled.

2. The construction process must allow different representations of the constructed object.

3. Object construction requires more steps than just "newing" up an object.

In essence, the Builder pattern provides flexibility and clarity, especially when dealing with complex object creation.


Comments