Decorator design provides flexible alternatives for sub-classing when inheritance is unfeasible like causing diamond dependency problem or massive code replications. This is commonly seen in object-oriented programming when a class is big and the algorithm variables/customization is small.
Decorator design pattern is something as such:
The golden rule for decorator design pattern is that the decorator function is always returning the same component type. They only perform enhancement/degradation alteration to the output of the component, before returning the final output to the client.
Inheritance has a limitation when the class is being inherited for multiple degree of orders. These are the common problems:
If a main class has more than 2 degree of order inheritance, inheriting/copying the main class is a massive memory wastage and code replications. Say if want to polymorph a function inside the main class, inheriting main class to create a new class types creates massive duplication over the sub-classes just to modify a function.
If we arrange the inheritance by multiple inheritance technique, we will get into the diamond inheritance problem easily.
Here is an example of an RPG gun effects in Go.
package main
import (
"fmt"
)
// ACOG is a decorator objects
type ACOG struct {
increaseHitRate int
}
func (a *ACOG) aim() int {
return a.increaseHitRate
}
// LRDS is a decoractor objects
type LRDS struct {
increaseHitRate int
}
func (a *LRDS) aim() int {
return a.increaseHitRate
}
// Enhancement is a decorator abstract interface
type Enhancement interface {
aim() int
}
// core weapon
type M16 struct {
hitRate int
enhancement Enhancement
}
func (w *M16) fire() int {
if w.enhancement != nil {
return w.hitRate + w.enhancement.aim()
}
return w.hitRate
}
func main() {
acog := &ACOG{increaseHitRate: 40}
lrds := &LRDS{increaseHitRate: 20}
m16 := &M16{hitRate: 50}
m16.enhancement = acog
fmt.Printf("M16 with ACOG: %v\n", m16.fire())
m16.enhancement = lrds
fmt.Printf("M16 with LRDS: %v\n", m16.fire())
m16.enhancement = nil
fmt.Printf("M16 without enhancement: %v\n", m16.fire())
}
// Output:
// M16 with ACOG: 90
// M16 with LRDS: 70
// M16 without enhancement: 50
Unlike other languages, Go does not have "class" concept. Hence, the enhancement is inside the core weapon using the interface type. the m16
object is able to equip any enhancement decorator objects that influences the fire()
function.
Another good example would be function modifiers in Go:
package main
import (
"fmt"
)
// functions
type a struct {
statement string
}
// decorator interface
type pkg struct {
}
func (p *pkg) prefix(prefix string, statement string) string {
return prefix + statement
}
func (p *pkg) suffix(suffix string, statement string) string {
return statement + suffix
}
func main() {
p := &pkg{}
s := &a{statement: "Hello World"}
fmt.Printf("pure : %v\n", s.statement)
fmt.Printf("prefix: %v\n", p.prefix("James, ", s.statement))
fmt.Printf("suffix: %v\n", p.suffix(", James.", s.statement))
}
// Output:
// pure : Hello World
// prefix: James, Hello World
// suffix: Hello World, James.
Notice that the pkg
structure is a set of decorator functions for string object. In this example, the pkg
structure prepend and append the original s.statement
string without altering s
object.
That's all about decorator design pattern.