Patterns: composition · 3 MIN READ
Decorator: add behavior while preserving the contract
Wrap a small operation instead of constructing a type hierarchy.
Wrap a small operation instead of constructing a type hierarchy. Decorator adds behavior around an object while retaining the interface clients already use. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Structural pattern · A MAP OF THE IDEA
- Caller sees the same operation
- Wrapper adds a concern
- Wrapped implementation performs the work
Useful when
- Cross-cutting behavior should compose
- A small interface makes wrapping clear
Watch out
- Order can change results and visibility
- Wrappers can hide optional capabilities
Implementation & operation cost: Low to medium
Fit depends on the task
Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.
The design decision
Decorator adds behavior around an object while retaining the interface clients already use. Go often expresses decoration with a function returning a function. Logging, metrics and authorization can use this shape, but wrapper order changes observable behavior.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"fmt"
"strings"
)
type Operation func(string) string
func Trim(next Operation) Operation {
return func(s string) string { return next(strings.TrimSpace(s)) }
}
func Bracket(next Operation) Operation { return func(s string) string { return "[" + next(s) + "]" } }
func main() {
identity := func(s string) string { return s }
fmt.Println(Trim(Bracket(identity))(" go "))
fmt.Println(Bracket(Trim(identity))(" go "))
}
// Output:
// [go]
// [go]
Both wrappers retain Operation and delegate exactly once. The shown input produces the same result for these two wrappers, but that does not prove commutativity: metrics around authorization may count rejected calls while metrics inside it may not.
Tradeoffs and the simpler alternative
Preserve cancellation, errors and optional interfaces that callers actually depend on. A wrapper around http.ResponseWriter can accidentally hide streaming capabilities. Use a direct call when one fixed concern does not need composable wrapping.
TAKE THIS WITH YOU
Adds behavior while retaining the consumer contract. Preserve cancellation, errors and optional interfaces that callers actually depend on. A wrapper around http.ResponseWriter can accidentally hide streaming capabilities. Use a direct call when one fixed concern does not need composable wrapping.
Check your understanding
Which statement best describes Decorator: add behavior while preserving the contract?
Go to the source
Progress stays in this browser. No account needed.