I. Decorator — Structural Pattern
The Decorator pattern is a structural design pattern that lets you attach new behaviors to objects by wrapping them inside decorator objects that share the same interface.
Instead of extending a class through inheritance, Decorator uses composition — a decorator wraps an existing object and adds behavior before or after delegating to it. Because decorators implement the same interface as the object they wrap, the client code never knows the difference.
Key properties:
- Same interface: decorators implement the same interface as the component they wrap
- Composable: decorators can be stacked — wrap a decorator inside another decorator
- Runtime flexibility: behaviors can be added or removed at runtime by choosing which decorators to apply
- Open/Closed Principle: add new behaviors without modifying existing code
Use Decorator when:
- You want to add responsibilities to objects without subclassing
- You need to combine behaviors in different ways at runtime
- Inheritance would produce an explosion of subclasses for every combination
II. Real-world Example
An HTTP client that needs logging and retry capabilities:
- The core
Httpinterface defines a single method:Get(url string) (interface{}, error)
FetchAdapteris the concrete implementation — it actually performs the HTTP request
LoggingDecoratorwraps anyHttpand logs requests before and after
RetryDecoratorwraps anyHttpand retries on failure up to N times
- Decorators can be chained:
RetryDecorator → LoggingDecorator → FetchAdapter
- The client just calls
.Get()on the outermost decorator — unaware of the chain

III. Implementation
http.go — the shared interface (reused from adapter package)
package decorator
import adapter "github.com/structural-patterns/adapter"
type Http = adapter.HttpThe Http interface from the adapter package:
type Http interface {
Get(url string) (interface{}, error)
}logging_decorator.go — adds logging around any Http call
package decorator
import "fmt"
type LoggingDecorator struct {
Inner Http
}
func (d *LoggingDecorator) Get(url string) (interface{}, error) {
fmt.Printf("Requesting %s\n", url)
response, err := d.Inner.Get(url)
if err != nil {
fmt.Printf("Request failed: %v\n", err)
return nil, err
}
fmt.Printf("Request succeeded: %s\n", url)
return response, nil
}retry_decorator.go — retries on failure up to N times
package decorator
import "time"
type RetryDecorator struct {
Inner Http
Retries int
}
func (d *RetryDecorator) Get(url string) (interface{}, error) {
var lastErr error
attempts := d.Retries + 1
for i := 0; i < attempts; i++ {
response, err := d.Inner.Get(url)
if err == nil {
return response, nil
}
lastErr = err
time.Sleep(time.Millisecond)
}
return nil, lastErr
}IV. Explain the example above
Let's trace through chaining both decorators:
base := &FetchAdapter{Instance: &Fetch{}}
logged := &LoggingDecorator{Inner: base}
retried := &RetryDecorator{Inner: logged, Retries: 3}
retried.Get("https://example.com")Output (success case):
Requesting https://example.com
Http Get with Fetch: https://example.com
Request succeeded: https://example.comStep by step:
retried.Get("https://example.com")—RetryDecoratorstarts attempt 1 of 4
- It calls
logged.Get("https://example.com")— delegates to itsInner
LoggingDecoratorlogs"Requesting https://example.com", then callsbase.Get()
FetchAdapterperforms the actual request, prints"Http Get with Fetch: ..."
- No error →
LoggingDecoratorlogs"Request succeeded"and returns
RetryDecoratorreceives success on first attempt — no retry needed
Output (failure + retry case):
Requesting https://broken-url.com
Request failed: connection refused
Requesting https://broken-url.com
Request failed: connection refused
... (up to Retries+1 times)Each retry goes through the full decorator chain again — logging each attempt. You can also use each decorator independently:
// Only logging, no retry
client := &LoggingDecorator{Inner: &FetchAdapter{Instance: &Fetch{}}}
client.Get("https://example.com")
// Only retry, no logging
client2 := &RetryDecorator{Inner: &FetchAdapter{Instance: &Fetch{}}, Retries: 2}
client2.Get("https://example.com")The key insight: decorators are interchangeable with the objects they decorate — both implement Http. This is what makes chaining possible. You can build any combination at runtime without changing a single struct definition.
V. Conclusion
The Decorator pattern solves the combinatorial explosion problem elegantly. Instead of creating LoggingFetchAdapter, RetryFetchAdapter, LoggingRetryFetchAdapter as separate classes, you compose them dynamically.
Trade-offs:
- Good fit: adding optional, composable behaviors to objects (logging, caching, retry, auth, rate-limiting); when inheritance would create too many subclasses; when behaviors need to be toggled at runtime
- Poor fit: when the order of decoration matters but is hard to reason about; deeply nested chains can make debugging harder — a stack trace through 5 decorators is less obvious than a single class
No pattern is universally superior. Use Decorator when you need flexible, composable behavior without touching existing code.
VI. References
- Go Design Patterns — Mario Castro Contreras



