Decorator Pattern in Go
This is the third chapter of the “Head First Design Patterns” book. I have covered the Strategy and Observer patterns before.
The problem
We are told to design an ordering application at a coffee shop. It sells a variety of beverages like
- House Blend
- Dark Roast
- Decaf
- Espresso
Apart from these, the customer has the choice to have condiments (add-ons) like
- Steamed milk
- Soy
- Mocha
- Whipped milk
Thought process
Approach 1
- What a noob coder like me would do is make an abstraction of
Beverage
, and derive concrete implementations of House Blend, Dark Roast, and so forth. - The problem arises with the condiments, using the above approach we are spoiled with choices and our implementations become
House Blend with steamed milk and mocha
Dark Roast with whip and soy
- Just imagine the number of possibilities,
#(Beverage) X #(condiments) X #(other condiments or not)
that is 4 * 4 * 4 = 64 ; that’s simply explosion of classes
Approach 2
- We maintain the boolean properties of every condiment for the
Beverage
abstraction. If the customer insists on adding one of these the respective boolean will be set to truthy value. - We have getters and setters for each of those properties.
- The number of implementations now is 4, and 1 abstraction.
- This reduces our implementations significantly, but we see some issues.
- If things like price and number of add-ons change, we will need to rewrite the implementations.
- A new beverage like iced tea can’t be enjoyed with the whip. But it will have the setter and getter for the same.
Approach 3
- Here we make use of the decorator pattern.
- The idea here is to keep on adding things as the customer wishes.
- Suppose the customer orders a “double mocha soy latte with the whip” with espresso.*
- We create espresso first, then keep putting our condiments in any order. e.g. Mocha, Soy Latte, Mocha, Whip
- The cost function is incremented at each step with the power of polymorphism.
* I am not sure how will that taste in real life.😅
UML Class Diagram
Takeaways
The book defines,
The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Decorators make our code openly extensible but closed for modification.
In simpler terms, we can add new condiments (decorators) without touching the code of existing beverages or condiments.
Language specifics
Go doesn’t use inheritance which saves us from some cognitive load.
We simply make an interface Beverager
that is implemented by the structs of beverages or condiments.
package utilstype Beverager interface {
Cost() float32
GetDescription() string
}
The implementation for Whip, a condiment.
package utils// Whip : Condiment
type Whip struct {
beverage Beverager
description string
cost float32
}// NewWhip : Constructor
func NewWhip(b Beverager) Beverager {
m := Whip{b, "Whip", 0.10}
return &m
}func (w *Whip) Cost() float32 {
return w.beverage.Cost() + w.cost
}func (w *Whip) GetDescription() string {
return w.beverage.GetDescription() + ", " + w.description
}
The implementation of a beverage like DarkRoast
differ from condiments by the fact
- it does NOT accept another beverage on its creation. This makes sense, we should not mix DarkRoast with Espresso!
One thing to note in the decorator patterns is that the order does not matter while adding condiments. In the above diagram, we could have wrapped the DarkRoast
with Mocha
followed by Whip
.
Output
The above code prints
Espresso = ₹ 1.99
Dark Roast, Mocha, Mocha, Whip = ₹ 1.4900001
House Blend, Soy, Mocha, Whip = ₹ 1.34
Wow, that’s cheap coffee! In reality, it costs a lot more. Please feel free to buy me some real coffee!!
View all the code in my Github repository.
Do you see a mistake in there? Mention in the comments.
Did you like it? Show your support through claps 👏.
I’m Srinjoy Santra, currently an SDE-1 at BookMyShow. You can connect with me on LinkedIn, visit my Github account, or follow me on Twitter.