Overview
The Decorator Pattern provides a flexible alternative to subclassing for extending functionality. Instead of creating inheritance hierarchies for each combination of features, decorators wrap objects and add behavior incrementally at runtime. Each decorator implements the same interface as the components it decorates, maintaining transparency to clients.
The pattern originated from the Gang of Four design patterns catalog as a solution to the class explosion problem that occurs when using inheritance to add features. When multiple optional features can combine in various ways, creating subclasses for each combination becomes unmaintainable. A system with five optional features would require 32 subclasses to cover all combinations.
Decorators compose behavior by wrapping one component with another. Each decorator adds one specific responsibility while delegating the core functionality to the wrapped component. Multiple decorators can wrap the same component, with each adding its own layer of behavior. The outermost decorator receives the initial request and passes it through the chain, with each decorator performing its operation before or after delegating.
# Basic decorator structure
class Component
def operation
"Base operation"
end
end
class Decorator
def initialize(component)
@component = component
end
def operation
@component.operation
end
end
class ConcreteDecorator < Decorator
def operation
"Added behavior: #{@component.operation}"
end
end
component = Component.new
decorated = ConcreteDecorator.new(component)
decorated.operation
# => "Added behavior: Base operation"
The pattern separates concerns by placing each optional feature in its own class. Components handle core functionality while decorators handle optional features. This separation follows the Single Responsibility Principle and makes both components and decorators easier to understand and maintain.
Key Principles
The Decorator Pattern operates on four core principles: interface consistency, composition over inheritance, transparent wrapping, and recursive composition.
Interface Consistency
Decorators and components implement the same interface. Clients interact with decorated objects using the same methods they would use with undecorated objects. This transparency allows decorators to substitute for components without requiring client code changes. The interface defines the contract that both components and decorators must fulfill.
# All participants share the same interface
module DataSource
def read_data
raise NotImplementedError
end
def write_data(data)
raise NotImplementedError
end
end
class FileDataSource
include DataSource
def initialize(filename)
@filename = filename
end
def read_data
File.read(@filename)
end
def write_data(data)
File.write(@filename, data)
end
end
class EncryptionDecorator
include DataSource
def initialize(source)
@source = source
end
def read_data
decrypt(@source.read_data)
end
def write_data(data)
@source.write_data(encrypt(data))
end
private
def encrypt(data)
data.reverse
end
def decrypt(data)
data.reverse
end
end
Composition Over Inheritance
The pattern favors object composition instead of class inheritance for extending functionality. Each decorator contains a reference to a component rather than inheriting from it. This approach provides more flexibility than inheritance because decorators can combine at runtime in any order and quantity. Inheritance creates static relationships at compile time, while composition creates dynamic relationships at runtime.
Transparent Wrapping
Decorators wrap components without exposing the wrapping mechanism to clients. From the client's perspective, a decorated object behaves like any other object implementing the interface. The decorator forwards requests to the wrapped component after adding its own behavior. This forwarding happens transparently, maintaining the abstraction boundary.
Recursive Composition
Decorators can wrap other decorators because both implement the same interface. This recursive structure creates chains where each decorator adds behavior and delegates to the next component in the chain. The chain terminates with a concrete component that implements the actual functionality. Each decorator in the chain processes the request during forwarding, either before delegation (pre-processing) or after delegation (post-processing).
# Multiple decorators wrapping recursively
source = FileDataSource.new('data.txt')
encrypted = EncryptionDecorator.new(source)
compressed = CompressionDecorator.new(encrypted)
logged = LoggingDecorator.new(compressed)
# Request flows through: logged -> compressed -> encrypted -> source
logged.write_data("sensitive information")
The pattern adheres to the Open/Closed Principle by allowing systems to add new decorators without modifying existing components or decorators. Each new decorator extends functionality while preserving the original component interface.
Ruby Implementation
Ruby provides multiple approaches for implementing decorators, from explicit decorator classes to modules and SimpleDelegator. Each approach offers different trade-offs between explicitness and conciseness.
Explicit Decorator Classes
The traditional approach defines a base decorator class that wraps a component and implements the component interface. Concrete decorators inherit from the base decorator and override specific methods.
class CoffeeComponent
def cost
2.0
end
def description
"Coffee"
end
end
class CoffeeDecorator
def initialize(coffee)
@coffee = coffee
end
def cost
@coffee.cost
end
def description
@coffee.description
end
end
class MilkDecorator < CoffeeDecorator
def cost
@coffee.cost + 0.5
end
def description
"#{@coffee.description}, Milk"
end
end
class SugarDecorator < CoffeeDecorator
def cost
@coffee.cost + 0.2
end
def description
"#{@coffee.description}, Sugar"
end
end
coffee = CoffeeComponent.new
with_milk = MilkDecorator.new(coffee)
with_milk_and_sugar = SugarDecorator.new(with_milk)
with_milk_and_sugar.cost
# => 2.7
with_milk_and_sugar.description
# => "Coffee, Milk, Sugar"
Module-Based Decoration
Ruby's module system enables decoration through dynamic module inclusion. This approach extends objects at runtime by mixing in modules that add behavior. The extend method adds module methods as singleton methods on individual objects without affecting the class or other instances.
module Markdown
def render
"**#{super}**"
end
end
module HTMLEscape
def render
super.gsub('<', '<').gsub('>', '>')
end
end
class TextContent
attr_reader :text
def initialize(text)
@text = text
end
def render
@text
end
end
content = TextContent.new("<Hello>")
content.extend(HTMLEscape)
content.extend(Markdown)
content.render
# => "**<Hello>**"
SimpleDelegator
Ruby's standard library includes SimpleDelegator, which provides automatic method forwarding. Decorators inherit from SimpleDelegator and override only the methods they need to modify. All other methods forward automatically to the wrapped object.
require 'delegate'
class TimestampDecorator < SimpleDelegator
def write(data)
__getobj__.write("[#{Time.now}] #{data}")
end
end
class UppcaseDecorator < SimpleDelegator
def write(data)
__getobj__.write(data.upcase)
end
end
class Logger
def write(message)
puts message
end
end
logger = Logger.new
timestamped = TimestampDecorator.new(logger)
uppercased = UppcaseDecorator.new(timestamped)
uppercased.write("error occurred")
# Output: [2025-10-06 14:30:45] ERROR OCCURRED
Refinements for Scoped Decoration
Refinements provide lexically scoped modifications to classes. Unlike global monkey patching, refinements apply only within the scope where they are activated using using. This approach suits scenarios requiring temporary behavior modification.
module StringFormatting
refine String do
def emphasize
"*** #{self} ***"
end
end
end
class Formatter
using StringFormatting
def format(text)
text.emphasize
end
end
formatter = Formatter.new
formatter.format("Important")
# => "*** Important ***"
"Important".emphasize
# => NoMethodError: undefined method 'emphasize'
Method Aliasing
Method aliasing creates decorators by renaming original methods and defining new methods that add behavior before calling the original. This technique modifies classes directly rather than wrapping instances.
class Calculator
def add(a, b)
a + b
end
end
class Calculator
alias_method :add_without_logging, :add
def add(a, b)
result = add_without_logging(a, b)
puts "Added #{a} and #{b}, result: #{result}"
result
end
end
calc = Calculator.new
calc.add(3, 4)
# Output: Added 3 and 4, result: 7
# => 7
Practical Examples
The Decorator Pattern applies to scenarios requiring flexible feature combinations without creating inheritance hierarchies.
HTTP Client with Optional Features
An HTTP client supports optional features like authentication, caching, logging, and retry logic. Each feature exists as a decorator, allowing clients to compose only needed functionality.
class HTTPClient
def get(url)
# Simulate HTTP request
{ status: 200, body: "Response from #{url}" }
end
def post(url, data)
{ status: 201, body: "Created" }
end
end
class AuthenticationDecorator
def initialize(client, token)
@client = client
@token = token
end
def get(url)
puts "Adding auth header: Bearer #{@token}"
@client.get(url)
end
def post(url, data)
puts "Adding auth header: Bearer #{@token}"
@client.post(url, data)
end
end
class CachingDecorator
def initialize(client)
@client = client
@cache = {}
end
def get(url)
if @cache.key?(url)
puts "Cache hit for #{url}"
@cache[url]
else
puts "Cache miss for #{url}"
@cache[url] = @client.get(url)
end
end
def post(url, data)
@cache.clear
@client.post(url, data)
end
end
class RetryDecorator
def initialize(client, max_retries: 3)
@client = client
@max_retries = max_retries
end
def get(url)
attempt = 0
begin
@client.get(url)
rescue => e
attempt += 1
retry if attempt < @max_retries
raise
end
end
def post(url, data)
attempt = 0
begin
@client.post(url, data)
rescue => e
attempt += 1
retry if attempt < @max_retries
raise
end
end
end
# Compose features as needed
client = HTTPClient.new
authenticated = AuthenticationDecorator.new(client, "abc123")
cached = CachingDecorator.new(authenticated)
resilient = RetryDecorator.new(cached)
resilient.get("https://api.example.com/users")
# Output: Cache miss for https://api.example.com/users
# Adding auth header: Bearer abc123
# => {:status=>200, :body=>"Response from https://api.example.com/users"}
Stream Processing Pipeline
Data streams pass through decorators that transform content. Each decorator adds one transformation step, creating processing pipelines where data flows through multiple stages.
class DataStream
def initialize(data)
@data = data
end
def read
@data
end
end
class FilterDecorator
def initialize(stream, &condition)
@stream = stream
@condition = condition
end
def read
@stream.read.select(&@condition)
end
end
class MapDecorator
def initialize(stream, &transform)
@stream = stream
@transform = transform
end
def read
@stream.read.map(&@transform)
end
end
class TakeDecorator
def initialize(stream, count)
@stream = stream
@count = count
end
def read
@stream.read.take(@count)
end
end
# Build processing pipeline
numbers = DataStream.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
evens = FilterDecorator.new(numbers) { |n| n.even? }
doubled = MapDecorator.new(evens) { |n| n * 2 }
first_three = TakeDecorator.new(doubled, 3)
first_three.read
# => [4, 8, 12]
Text Rendering with Formatting
Text content passes through decorators that apply formatting transformations. Each decorator adds one type of formatting, allowing combinations like bold italic text or escaped HTML with line breaks.
class Text
attr_reader :content
def initialize(content)
@content = content
end
def render
@content
end
end
class BoldDecorator
def initialize(text)
@text = text
end
def render
"<b>#{@text.render}</b>"
end
end
class ItalicDecorator
def initialize(text)
@text = text
end
def render
"<i>#{@text.render}</i>"
end
end
class LinkDecorator
def initialize(text, url)
@text = text
@url = url
end
def render
"<a href=\"#{@url}\">#{@text.render}</a>"
end
end
class ParagraphDecorator
def initialize(text)
@text = text
end
def render
"<p>#{@text.render}</p>"
end
end
# Create formatted text
text = Text.new("Click here")
bold_text = BoldDecorator.new(text)
link = LinkDecorator.new(bold_text, "https://example.com")
paragraph = ParagraphDecorator.new(link)
paragraph.render
# => "<p><a href=\"https://example.com\"><b>Click here</b></a></p>"
Notification System with Channels
A notification system sends messages through multiple channels. Each decorator adds a delivery channel while preserving the core notification functionality. Users configure which channels receive notifications based on message priority or user preferences.
class Notification
attr_reader :message
def initialize(message)
@message = message
end
def send
# Base notification does nothing
end
end
class EmailDecorator
def initialize(notification, email)
@notification = notification
@email = email
end
def send
puts "Sending email to #{@email}: #{@notification.message}"
@notification.send
end
end
class SMSDecorator
def initialize(notification, phone)
@notification = notification
@phone = phone
end
def send
puts "Sending SMS to #{@phone}: #{@notification.message}"
@notification.send
end
end
class SlackDecorator
def initialize(notification, channel)
@notification = notification
@channel = channel
end
def send
puts "Posting to Slack #{@channel}: #{@notification.message}"
@notification.send
end
end
# Send critical alert through all channels
alert = Notification.new("System failure detected")
with_email = EmailDecorator.new(alert, "admin@example.com")
with_sms = SMSDecorator.new(with_email, "+1234567890")
with_slack = SlackDecorator.new(with_sms, "#alerts")
with_slack.send
# Output: Posting to Slack #alerts: System failure detected
# Sending SMS to +1234567890: System failure detected
# Sending email to admin@example.com: System failure detected
Design Considerations
Selecting between decorators and alternatives depends on the required flexibility, number of feature combinations, and runtime versus compile-time composition needs.
When to Use Decorators
Decorators fit scenarios with multiple optional features that combine in various ways. A reporting system might support optional features for pagination, sorting, filtering, and formatting. Creating subclasses for each combination produces exponential class growth. Decorators compose these features independently at runtime.
Systems requiring runtime behavior modification benefit from decorators. A web application might apply different security policies based on user roles or request context. Decorators add appropriate validation and authorization without modifying the core request handling logic.
Alternatives to Decorators
Strategy Pattern provides an alternative when selecting between different algorithms rather than combining features. A payment processor might support multiple payment methods, but each transaction uses exactly one method. Strategy Pattern swaps entire algorithms while Decorator Pattern combines incremental behaviors.
Inheritance works when the feature set is fixed and small. A graphics system with shape classes might support filled and unfilled variants through two subclasses. Adding more orthogonal features like borders, shadows, or patterns would require decorators to avoid class explosion.
Chain of Responsibility passes requests through a chain of handlers where each decides whether to process or forward the request. Decorators always process and forward, while Chain of Responsibility handlers may terminate the chain. Logging systems use Chain of Responsibility when different log levels route to different handlers.
Trade-offs
Decorators increase object count and indirection. Each decorator adds a wrapper object, and deeply nested decorators create long delegation chains. Performance-sensitive code might suffer from the overhead of multiple method calls. Profiling identifies when decorator chains become bottlenecks.
# Decorator overhead example
class Component
def operation
# Simple operation
end
end
# Each decorator adds a method call
decorated = Decorator1.new(
Decorator2.new(
Decorator3.new(
Decorator4.new(Component.new)
)
)
)
# Operation now involves 5 method calls instead of 1
decorated.operation
Decorators complicate debugging because stack traces include all decorators in the chain. Finding the source of behavior requires understanding which decorators are active and their order. Explicit naming conventions and logging help track decorator chains during development.
Interface pollution occurs when decorators must implement large interfaces but only modify a few methods. Each decorator must forward all unmodified methods, creating repetitive boilerplate. SimpleDelegator or method_missing reduces this burden in Ruby but can hide errors.
Decorator Ordering
Decorator order affects behavior when decorators interact. An encryption decorator applied before a compression decorator produces different results than compression applied before encryption. Systems must document decorator dependencies and enforce correct ordering.
# Order matters for interacting decorators
source = FileDataSource.new('data.txt')
# Compress then encrypt
compressed_first = EncryptionDecorator.new(
CompressionDecorator.new(source)
)
# Encrypt then compress
encrypted_first = CompressionDecorator.new(
EncryptionDecorator.new(source)
)
# These produce different results
compressed_first.write_data("test")
encrypted_first.write_data("test")
Common Patterns
Several decorator patterns address recurring design needs and implementation challenges.
Transparent Decorator
Transparent decorators forward all interface methods to wrapped components. Only overridden methods add behavior, while all other methods delegate unchanged. This pattern maintains full interface compatibility and allows decorators to appear anywhere in a chain.
require 'delegate'
class TransparentDecorator < SimpleDelegator
def enhanced_method
"Enhanced: #{__getobj__.enhanced_method}"
end
# All other methods forward automatically
end
class Component
def enhanced_method
"base"
end
def other_method
"other"
end
def another_method
"another"
end
end
component = Component.new
decorated = TransparentDecorator.new(component)
decorated.enhanced_method # => "Enhanced: base"
decorated.other_method # => "other"
decorated.another_method # => "another"
Half-Object Plus Protocol
This pattern splits decorator responsibilities between a wrapper (half-object) and a protocol for accessing the wrapped object. The decorator implements the protocol for retrieving the wrapped component, allowing introspection and unwrapping.
module Decorable
def component
@component
end
def base_component
current = self
current = current.component while current.respond_to?(:component)
current
end
end
class Decorator
include Decorable
def initialize(component)
@component = component
end
end
class ConcreteDecorator < Decorator
def operation
"decorated: #{@component.operation}"
end
end
component = Component.new
decorated = ConcreteDecorator.new(ConcreteDecorator.new(component))
decorated.base_component == component # => true
Conditional Decorator
Conditional decorators apply behavior based on runtime conditions. The decorator checks conditions before deciding whether to apply its behavior or delegate directly.
class ConditionalCachingDecorator
def initialize(component, cache_condition)
@component = component
@cache_condition = cache_condition
@cache = {}
end
def fetch(key)
if @cache_condition.call(key)
@cache[key] ||= @component.fetch(key)
else
@component.fetch(key)
end
end
end
repository = DataRepository.new
# Only cache GET requests
cached = ConditionalCachingDecorator.new(
repository,
->(key) { key.start_with?('GET:') }
)
cached.fetch('GET:/users') # Cached
cached.fetch('POST:/users') # Not cached
Composite Decorator
Composite decorators combine multiple decorators into a single unit. Instead of wrapping components multiple times, a composite decorator applies several transformations internally.
class CompositeDecorator
def initialize(component, decorators)
@component = component
@decorators = decorators
end
def operation
@decorators.reduce(@component) do |comp, decorator_class|
decorator_class.new(comp)
end.operation
end
end
component = Component.new
composite = CompositeDecorator.new(
component,
[LoggingDecorator, TimingDecorator, ValidationDecorator]
)
composite.operation
Decorator Factory
Factories construct decorated objects based on configuration. The factory encapsulates decorator selection and ordering logic, providing a simpler interface for clients.
class HTTPClientFactory
def self.build(options = {})
client = HTTPClient.new
client = AuthenticationDecorator.new(client, options[:token]) if options[:auth]
client = CachingDecorator.new(client) if options[:cache]
client = RetryDecorator.new(client, max_retries: options[:retries]) if options[:retry]
client = LoggingDecorator.new(client) if options[:log]
client
end
end
# Simple interface for complex decoration
client = HTTPClientFactory.build(
auth: true,
cache: true,
retry: true,
token: 'abc123',
retries: 3
)
Memoizing Decorator
Memoizing decorators cache computation results to avoid redundant processing. The decorator checks its cache before delegating to the wrapped component, storing results for future requests.
class MemoizingDecorator
def initialize(component)
@component = component
@cache = {}
end
def compute(input)
@cache[input] ||= @component.compute(input)
end
def clear_cache
@cache.clear
end
end
expensive_calculator = ExpensiveCalculator.new
memoized = MemoizingDecorator.new(expensive_calculator)
memoized.compute(42) # Calculates
memoized.compute(42) # Returns cached result
Common Pitfalls
Decorators introduce several categories of errors related to interface compatibility, ordering, and state management.
Interface Violations
Decorators must implement the complete interface of components they wrap. Partial implementations break when clients call methods the decorator does not override. This problem appears when interfaces grow and existing decorators do not update.
# Problem: Incomplete interface implementation
class Component
def method_a
"A"
end
def method_b
"B"
end
def method_c
"C"
end
end
class IncompleteDecorator
def initialize(component)
@component = component
end
def method_a
"Decorated #{@component.method_a}"
end
# Missing method_b and method_c
end
component = Component.new
decorated = IncompleteDecorator.new(component)
decorated.method_a # => "Decorated A"
decorated.method_b # => NoMethodError
Solution: Use SimpleDelegator for automatic forwarding or implement method_missing to catch unhandled methods.
# Solution: Complete delegation
require 'delegate'
class CompleteDecorator < SimpleDelegator
def method_a
"Decorated #{__getobj__.method_a}"
end
# method_b and method_c forward automatically
end
State Leakage Between Decorators
Decorators that maintain state can cause unexpected behavior when state from one decorator affects another. Stateful decorators must isolate their state from the wrapped component and other decorators.
# Problem: State leakage
class CountingDecorator
@@count = 0 # Class variable shared across instances
def initialize(component)
@component = component
end
def operation
@@count += 1
@component.operation
end
end
decorator1 = CountingDecorator.new(Component.new)
decorator2 = CountingDecorator.new(Component.new)
decorator1.operation # @@count = 1
decorator2.operation # @@count = 2, unexpected cross-contamination
Solution: Use instance variables and ensure proper initialization.
# Solution: Isolated state
class CountingDecorator
def initialize(component)
@component = component
@count = 0
end
def operation
@count += 1
@component.operation
end
end
Incorrect Decorator Ordering
Decorators applied in the wrong order produce incorrect results. Compression applied after encryption compresses already-encrypted data, while encryption applied after compression encrypts already-compressed data. These produce different outcomes and may affect security or efficiency.
# Order-dependent behavior
class EncryptionDecorator
def write(data)
@component.write(encrypt(data))
end
end
class CompressionDecorator
def write(data)
@component.write(compress(data))
end
end
# Wrong order: encrypts compressed data
source = FileSource.new
compressed = CompressionDecorator.new(source)
encrypted = EncryptionDecorator.new(compressed)
# Correct order: compresses encrypted data
source = FileSource.new
encrypted = EncryptionDecorator.new(source)
compressed = CompressionDecorator.new(encrypted)
Excessive Decorator Depth
Deep decorator chains increase complexity and hurt performance. Each decorator adds overhead for method dispatch and object creation. Systems with more than five layers of decorators often indicate design problems.
Solution: Group related decorators into composite decorators or reconsider the design.
Forgetting Super Calls
Module-based decorators must call super to invoke the next method in the chain. Forgetting super terminates the chain prematurely, breaking functionality.
# Problem: Missing super
module Decorator
def operation
"Decorated" # Forgot to call super
end
end
class Component
def operation
"Base"
end
end
component = Component.new
component.extend(Decorator)
component.operation # => "Decorated", lost base functionality
Solution: Always call super unless intentionally overriding.
# Solution: Include super
module Decorator
def operation
"Decorated: #{super}"
end
end
component = Component.new
component.extend(Decorator)
component.operation # => "Decorated: Base"
Type Checking Failures
Code that checks object types using instance_of? or class fails with decorated objects because decorators wrap the original type. Type checks should use is_a? or check for interface compliance rather than concrete types.
# Problem: Type checking decorated objects
class AuthDecorator
def initialize(user)
@user = user
end
def name
@user.name
end
end
user = User.new("Alice")
decorated = AuthDecorator.new(user)
user.instance_of?(User) # => true
decorated.instance_of?(User) # => false, breaks type checks
Solution: Use duck typing or respond_to? to check capabilities.
# Solution: Duck typing
def process_user(user)
if user.respond_to?(:name)
puts user.name
end
end
process_user(decorated) # Works regardless of decoration
Reference
Core Components
| Component | Purpose | Responsibility |
|---|---|---|
| Component | Defines interface | Declares operations for objects |
| ConcreteComponent | Implements base object | Provides core functionality |
| Decorator | Maintains component reference | Forwards requests to component |
| ConcreteDecorator | Adds specific behavior | Extends component functionality |
Ruby Implementation Approaches
| Approach | Syntax | Use When |
|---|---|---|
| Class-based | class Decorator; def initialize(component) | Need explicit class hierarchy |
| Module-based | module Decorator; def method; super | Want runtime extension per instance |
| SimpleDelegator | class Decorator < SimpleDelegator | Want automatic method forwarding |
| Refinements | module M; refine C do | Need lexically scoped modifications |
| Method aliasing | alias_method :old, :new | Modifying existing classes |
Decorator vs Alternatives
| Pattern | Purpose | Key Difference |
|---|---|---|
| Decorator | Add responsibilities | Composes behaviors incrementally |
| Strategy | Swap algorithms | Replaces entire algorithm |
| Proxy | Control access | Adds indirection, same interface |
| Adapter | Convert interfaces | Changes interface to match client |
| Chain of Responsibility | Handle requests | May terminate chain early |
Design Decision Matrix
| Scenario | Choose Decorator When | Choose Alternative When |
|---|---|---|
| Feature combinations | Multiple optional features combine | Single feature selection needed |
| Modification time | Runtime composition required | Compile-time inheritance sufficient |
| Number of combinations | Exponential combinations (2^n) | Linear combinations (n) |
| Interface stability | Interface remains constant | Interface varies by type |
| Transparency | Clients unaware of decoration | Clients need decoration knowledge |
Common Decorator Applications
| Domain | Decorator Type | Purpose |
|---|---|---|
| I/O streams | BufferedDecorator | Add buffering to streams |
| I/O streams | EncryptionDecorator | Encrypt data transparently |
| I/O streams | CompressionDecorator | Compress data automatically |
| UI components | ScrollableDecorator | Add scrolling to components |
| UI components | BorderDecorator | Add borders to components |
| Web requests | AuthenticationDecorator | Add auth headers |
| Web requests | CachingDecorator | Cache responses |
| Web requests | LoggingDecorator | Log requests and responses |
| Notifications | EmailDecorator | Send via email |
| Notifications | SMSDecorator | Send via SMS |
| Text processing | FormattingDecorator | Apply text formatting |
| Text processing | ValidationDecorator | Validate content |
Performance Characteristics
| Aspect | Impact | Mitigation |
|---|---|---|
| Method calls | O(n) overhead for n decorators | Limit decorator depth to 5 |
| Memory | One object per decorator | Use flyweight for shared decorators |
| Object creation | Allocation cost per decorator | Pool decorator instances |
| Cache efficiency | Poor locality with deep chains | Flatten frequently-used chains |
Anti-Patterns
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Decorator explosion | Too many decorator classes | Use composite decorators |
| Order dependency | Specific order required | Document dependencies clearly |
| Stateful decorators | Shared mutable state | Isolate state per instance |
| Interface bloat | Decorators implement unused methods | Use SimpleDelegator |
| Deep nesting | More than 5 decorator layers | Refactor into composite |
| Type checking | instanceof checks fail | Use duck typing |