រចនាសម្ព័ន្ធ កម្រិតស្មុគស្មាញ: មធ្យម

Decorator in Go

Add behavior around an object dynamically by wrapping it with small focused layers that share the same interface.

The Problem

Cross-cutting concerns such as audit logging, secondary notifications, rate limiting, and tracing tend to pile onto otherwise simple services. If you keep modifying the base implementation for every concern, the core behavior becomes harder to test and reason about.

The Solution

Decorator wraps an object with another object that implements the same interface. In Go, decorators are usually small structs with a Next field. Each layer adds one concern before or after delegating to the wrapped object, and callers can compose only the layers they need.

Structure

  • Component interface: Notifier defines the behavior being extended.
  • Concrete component: EmailNotifier performs the base notification.
  • Decorators: Audit and SMS layers wrap another notifier and add behavior.
  • Client: Composes the stack explicitly at startup.

Implementation

This example starts with a plain email notifier, then wraps it with auditing and SMS delivery. Each decorator stays focused on one concern and delegates to the next layer in the chain.

package main

import "fmt"

type Notifier interface {
	Send(recipient string, message string)
}

type EmailNotifier struct{}

func (EmailNotifier) Send(recipient string, message string) {
	fmt.Printf("email to %s: %s\n", recipient, message)
}

Best Practices

  • Keep each decorator narrow so stacks stay understandable.
  • Prefer explicit composition in setup code over hidden automatic wrapping.
  • Make decorators preserve the same contract as the wrapped object.
  • Use decorators for additive behavior, not for changing the underlying domain meaning.
  • If wrappers start coordinating too many subsystems, you may really need a Facade or pipeline instead.

When to Use

  • You need optional layers such as logging, caching, secondary delivery, or tracing.
  • The base behavior should stay small and independently testable.
  • Different environments need different wrapper combinations.

When NOT to Use

  • The extra behavior is not optional and belongs in the core abstraction.
  • The wrapper stack would become opaque and difficult to debug.
  • A simple function call around the object would be clearer than a reusable decorator type.