Welcome back! You've just explored the Adapter Pattern, which helps incompatible interfaces work together seamlessly. As you continue learning about structural patterns in Go, it's time to dive into the Decorator Pattern.
The Decorator Pattern allows you to add new functionalities to an existing object without altering its structure. This is achieved by wrapping the object with decorator classes. This pattern is highly useful for extending functionalities in a flexible and reusable way.
Consider a coffee shop scenario where you start with a simple coffee and can add milk, sugar, and other ingredients. Each addition should not change the actual coffee object but should add new functionalities.
Here’s a look at how this can be implemented in Go:
Go1package main 2 3import ( 4 "fmt" 5) 6 7// Component interface 8type Coffee interface { 9 Cost() float64 10 Description() string 11} 12 13// ConcreteComponent 14type SimpleCoffee struct{} 15 16func (c *SimpleCoffee) Cost() float64 { 17 return 5.0 18} 19 20func (c *SimpleCoffee) Description() string { 21 return "Simple coffee" 22} 23 24// Decorator 25type CoffeeDecorator struct { 26 coffee Coffee 27} 28 29func (d *CoffeeDecorator) Cost() float64 { 30 return d.coffee.Cost() 31} 32 33func (d *CoffeeDecorator) Description() string { 34 return d.coffee.Description() 35} 36 37// ConcreteDecorators 38type MilkDecorator struct { 39 *CoffeeDecorator 40} 41 42func NewMilkDecorator(c Coffee) *MilkDecorator { 43 return &MilkDecorator{&CoffeeDecorator{c}} 44} 45 46func (d *MilkDecorator) Cost() float64 { 47 return d.CoffeeDecorator.Cost() + 1.0 48} 49 50func (d *MilkDecorator) Description() string { 51 return d.CoffeeDecorator.Description() + ", milk" 52} 53 54type SugarDecorator struct { 55 *CoffeeDecorator 56} 57 58func NewSugarDecorator(c Coffee) *SugarDecorator { 59 return &SugarDecorator{&CoffeeDecorator{c}} 60} 61 62func (d *SugarDecorator) Cost() float64 { 63 return d.CoffeeDecorator.Cost() + 0.5 64} 65 66func (d *SugarDecorator) Description() string { 67 return d.CoffeeDecorator.Description() + ", sugar" 68} 69 70func main() { 71 var coffee Coffee = &SimpleCoffee{} 72 fmt.Println(coffee.Description(), ":", coffee.Cost()) // Simple coffee : 5 73 74 coffee = NewMilkDecorator(coffee) 75 fmt.Println(coffee.Description(), ":", coffee.Cost()) // Simple coffee, milk : 6 76 77 coffee = NewSugarDecorator(coffee) 78 fmt.Println(coffee.Description(), ":", coffee.Cost()) // Simple coffee, milk, sugar : 6.5 79}
In this example:
SimpleCoffee
is a basic coffee implementation.CoffeeDecorator
wraps around a Coffee
interface and can be extended.MilkDecorator
and SugarDecorator
are concrete decorators adding milk and sugar functionalities to the coffee, respectively.By chaining decorators, you can add multiple functionalities to the base object without modifying its structure. This makes the Decorator Pattern a powerful tool for building flexible and extensible systems.
Let's break down the key components of the Decorator Pattern:
Coffee
is the component interface.Component
interface. In the example, SimpleCoffee
is the concrete component.Component
interface and provides additional functionalities. In the example, CoffeeDecorator
is the decorator class.Decorator
class and add specific functionalities. In the example, MilkDecorator
and SugarDecorator
are concrete decorators.Component
interface and can add functionalities using decorators. In the example, main
is the client code.The Decorator Pattern is useful in various scenarios where you need to add responsibilities to objects dynamically. Here are some common use cases:
Pros
Cons
Go emphasizes simplicity, clear structure, and efficiency. The Decorator Pattern aligns well with these goals when used appropriately by promoting:
With this there are few things to keep in mind:
Learning the Decorator Pattern is essential because it promotes flexibility and reusability in your code. Using this pattern, you can dynamically add new functionalities to an object without changing its structure, which is crucial for maintaining clean and manageable codebases.
Imagine you're developing an application that needs to support various features on the fly. The Decorator Pattern enables you to build these features incrementally without modifying the core functionality. This makes your application more scalable and easier to maintain.
Excited to learn how to implement and use the Decorator Pattern in Go? Let's jump into the practice section and get hands-on experience with this powerful design pattern.