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

  1. Caller sees the same operation
  2. Wrapper adds a concern
  3. 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

Small tool or CLI4/5
Domain-rich application5/5
Independent distributed services5/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

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.

02

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.

go
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]
Read the code

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.

03

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.