Decorator Design Pattern in Go

1. Definition

The Decorator Design Pattern allows you to add new functionalities to existing objects dynamically without altering its structure. It involves a set of decorator classes that are used to wrap concrete components. These decorators mirror the type of the components they decorate but add or override behavior.

2. Problem Statement

Imagine you have an existing component class and you want to add some additional behaviors to it, but you don't want to modify the original class. You could extend the class, but it might not be feasible if you have multiple combinations of behaviors that you want to add.

3. Solution

Instead of reworking the existing component or using inheritance, the decorator pattern allows you to wrap the original component in a new object that adds the behavior. This way, the original code remains unchanged, and you can combine decorators to get various feature combinations.

4. Real-World Use Cases

1. Adding window dressing or borders to a graphical window.

2. Middleware in web servers, where you decorate a request with multiple layers (e.g., logging, authentication).

3. Stream decorators in I/O operations, like adding buffering or encoding.

5. Implementation Steps

1. Define a component interface that both the original components and decorators will implement.

2. Create a concrete component class that implements this interface.

3. Develop one or more decorator classes that also implement the component interface. These classes should have a reference to a component and can add additional behavior when the interface methods are invoked.

6. Implementation in Go

package main
import "fmt"
// Step 1: Component Interface
type Beverage interface {
	Cost() float64
	Description() string
}
// Step 2: Concrete Component
type Coffee struct{}
func (c *Coffee) Cost() float64 {
	return 5.00
}
func (c *Coffee) Description() string {
	return "Coffee"
}
// Step 3: Decorator
type MilkDecorator struct {
	beverage Beverage
}
func (m *MilkDecorator) Cost() float64 {
	return m.beverage.Cost() + 1.50
}
func (m *MilkDecorator) Description() string {
	return m.beverage.Description() + ", Milk"
}
// Client code
func main() {
	coffee := &Coffee{}
	fmt.Printf("Cost: %.2f\n", coffee.Cost())
	fmt.Println("Description:", coffee.Description())
	coffeeWithMilk := &MilkDecorator{beverage: coffee}
	fmt.Printf("Cost: %.2f\n", coffeeWithMilk.Cost())
	fmt.Println("Description:", coffeeWithMilk.Description())
}

Output:

Cost: 5.00
Description: Coffee
Cost: 6.50
Description: Coffee, Milk

Explanation:

1. The Beverage interface defines methods any beverage (or its decorator) should have.

2. Coffee is our concrete component. It implements the Beverage interface.

3. The MilkDecorator is our decorator here. It also implements the Beverage interface but has a reference to another beverage. When the methods are called, it delegates to the wrapped beverage and adds its own behavior.

4. In the client code, we first create a coffee and then decorate it with milk, demonstrating the dynamic addition of behavior.

7. When to use?

Use the Decorator Pattern when:

1. You want to add responsibilities to individual objects dynamically and transparently, without affecting other objects.

2. Extending functionality using subclassing is impractical because it results in too many subclasses and complicates the class hierarchy.

3. You want to keep new functionality separate, or when responsibilities can be withdrawn.


Comments