CrackedRuby CrackedRuby

Behavioral Patterns Overview

Overview

Behavioral patterns form one of three categories in the Gang of Four design pattern taxonomy, alongside creational and structural patterns. These patterns address how objects interact and distribute responsibilities, focusing on the communication and collaboration between objects rather than their composition or creation.

The behavioral pattern category emerged from the observation that object-oriented systems require more than just proper object structure. Objects must coordinate their actions, delegate responsibilities, and manage complex workflows. Behavioral patterns provide solutions for these interaction challenges by encapsulating algorithms, defining communication protocols, and establishing clear responsibility chains.

These patterns operate at different levels of granularity. Some, like Strategy and Command, encapsulate individual algorithms or requests. Others, like Observer and Mediator, define communication structures between multiple objects. Template Method and State operate on object behavior over time, while Iterator and Visitor separate algorithms from the data structures they operate on.

Ruby's dynamic nature and features like blocks, procs, and module mixins make it particularly suited for implementing behavioral patterns. Many patterns that require verbose implementations in static languages become concise and expressive in Ruby. First-class functions through blocks and procs eliminate the need for separate command objects in many cases, while modules provide mixin-based behavior composition.

# Behavioral patterns define object communication
# Strategy pattern encapsulates algorithms
class PaymentProcessor
  def initialize(strategy)
    @strategy = strategy
  end
  
  def process(amount)
    @strategy.call(amount)
  end
end

credit_card = ->(amount) { "Charging $#{amount} to card" }
processor = PaymentProcessor.new(credit_card)
processor.process(100)
# => "Charging $100 to card"

The distinction between behavioral and other pattern types centers on intent. Creational patterns abstract object instantiation, structural patterns compose objects into larger structures, and behavioral patterns assign responsibilities for communication and algorithm management. A pattern qualifies as behavioral if its primary purpose involves object interaction protocols or responsibility distribution.

Key Principles

Behavioral patterns share several foundational principles that distinguish them from other pattern categories. Understanding these principles helps in pattern selection and implementation.

Encapsulation of Variation: Behavioral patterns encapsulate aspects of behavior that vary. Strategy encapsulates algorithms, State encapsulates state-dependent behavior, and Command encapsulates requests. This encapsulation allows variation points to change independently of the contexts that use them. The principle follows the "encapsulate what varies" maxim from design pattern literature.

Inversion of Control: Many behavioral patterns invert the usual control flow. Template Method defines algorithm structure but delegates specific steps to subclasses. Observer inverts dependencies so subjects notify observers without knowing their concrete types. This inversion reduces coupling and increases flexibility by allowing the framework to call application code rather than the reverse.

Composition Over Inheritance: Behavioral patterns favor object composition and delegation over class inheritance. Strategy composes contexts with algorithm objects, Command composes invokers with command objects, and Chain of Responsibility chains handler objects. This composition provides greater flexibility than inheritance-based solutions because behaviors can change at runtime and combine in ways not anticipated at design time.

# Template Method uses inheritance
class ReportGenerator
  def generate
    gather_data
    format_output
    send_report
  end
  
  def gather_data
    raise NotImplementedError
  end
  
  def format_output
    raise NotImplementedError
  end
end

# Strategy uses composition
class ReportGenerator
  def initialize(data_gatherer, formatter)
    @data_gatherer = data_gatherer
    @formatter = formatter
  end
  
  def generate
    data = @data_gatherer.gather
    @formatter.format(data)
  end
end

Single Responsibility for Interactions: Each behavioral pattern addresses one specific interaction concern. Observer handles one-to-many notifications, Mediator manages many-to-many communications, Chain of Responsibility processes requests sequentially. This focused responsibility makes patterns composable and easier to understand.

Protocol Definition: Behavioral patterns define protocols or interfaces for object communication. These protocols specify how objects interact without dictating their internal implementation. Observer defines the subject-observer protocol, Command defines the command execution protocol, and Visitor defines the element-visitor double-dispatch protocol.

Temporal Coupling Management: Several behavioral patterns manage temporal relationships between operations. Memento captures object state at specific points in time, Command queues operations for delayed execution, and State changes behavior based on temporal progression through states. This temporal management separates when operations execute from how they execute.

The relationship between these principles creates a design philosophy where objects collaborate through well-defined protocols, responsibilities remain focused and cohesive, and variation points receive proper encapsulation. Ruby's features align naturally with these principles, making pattern implementations more concise than in languages requiring explicit interface declarations or lacking first-class functions.

Common Patterns

The behavioral pattern catalog contains eleven distinct patterns, each addressing specific interaction and responsibility distribution scenarios. Understanding each pattern's intent and structure enables appropriate pattern selection.

Chain of Responsibility decouples request senders from receivers by giving multiple objects a chance to handle requests. The pattern chains handler objects and passes requests along the chain until a handler processes the request or the chain ends. Middleware stacks in web frameworks exemplify this pattern, where each middleware can process a request, modify it, or pass it to the next handler.

Command encapsulates requests as objects, parameterizing clients with different requests and supporting undoable operations. The pattern transforms method calls into standalone objects containing all information needed to execute the method later. Ruby blocks and procs often replace explicit command objects, but the Command pattern remains valuable when commands require complex lifecycle management or persistence.

# Command pattern for undo/redo
class TextEditor
  def initialize
    @text = ""
    @history = []
  end
  
  def execute(command)
    command.execute(@text)
    @history << command
  end
  
  def undo
    command = @history.pop
    command.undo(@text)
  end
end

class InsertCommand
  def initialize(position, char)
    @position = position
    @char = char
  end
  
  def execute(text)
    text.insert(@position, @char)
  end
  
  def undo(text)
    text.slice!(@position)
  end
end

Iterator provides sequential access to collection elements without exposing the collection's internal representation. Ruby's Enumerable module implements this pattern pervasively, with methods like each, map, and select defining iteration protocols. Custom collections include Enumerable and define each to gain iterator functionality.

Mediator defines an object that encapsulates how a set of objects interact, promoting loose coupling by preventing objects from referring to each other explicitly. The pattern centralizes complex communications and control logic between related objects. Form validation systems often use mediators to coordinate field validations without fields directly referencing each other.

Memento captures and externalizes an object's internal state without violating encapsulation, enabling state restoration. The pattern provides state snapshots that can restore objects to previous states. Version control systems and undo mechanisms implement memento-like structures.

# Memento for state snapshots
class DocumentMemento
  attr_reader :content, :timestamp
  
  def initialize(content)
    @content = content.dup
    @timestamp = Time.now
  end
end

class Document
  attr_accessor :content
  
  def create_memento
    DocumentMemento.new(@content)
  end
  
  def restore(memento)
    @content = memento.content.dup
  end
end

Observer defines a one-to-many dependency between objects so that when one object changes state, all dependents receive notification and update automatically. Ruby's Observable module implements this pattern, though many modern Ruby applications use pub-sub libraries or reactive programming frameworks instead.

State allows an object to alter its behavior when its internal state changes, appearing to change its class. The pattern encapsulates state-specific behavior in separate state objects. Connection management, protocol implementations, and workflow systems frequently use the State pattern.

# State pattern for connection lifecycle
class TCPConnection
  def initialize
    @state = ClosedState.new(self)
  end
  
  def open
    @state.open
  end
  
  def close
    @state.close
  end
  
  def change_state(state)
    @state = state
  end
end

class OpenState
  def initialize(connection)
    @connection = connection
  end
  
  def open
    "Already open"
  end
  
  def close
    @connection.change_state(ClosedState.new(@connection))
    "Connection closed"
  end
end

class ClosedState
  def initialize(connection)
    @connection = connection
  end
  
  def open
    @connection.change_state(OpenState.new(@connection))
    "Connection opened"
  end
  
  def close
    "Already closed"
  end
end

Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. The pattern lets algorithms vary independently from clients that use them. Sorting with different comparison strategies, compression with different algorithms, and validation with different rule sets demonstrate this pattern.

Template Method defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. Subclasses redefine certain algorithm steps without changing the algorithm's structure. Ruby's hook methods like inherited, included, and method_missing often implement template method-like patterns.

Visitor represents an operation to be performed on elements of an object structure, letting you define new operations without changing the element classes. The pattern uses double dispatch to route operations based on both the visitor type and element type. Abstract syntax tree processing and document rendering systems commonly use visitors.

These eleven patterns address different behavioral concerns but share the common goal of managing object interactions and responsibility distribution. Pattern selection depends on the specific communication structure, flexibility requirements, and whether the focus lies on algorithms, requests, state transitions, or element traversal.

Ruby Implementation

Ruby's language features enable concise behavioral pattern implementations that often require less boilerplate than implementations in static languages. Understanding how Ruby features map to pattern concepts helps create idiomatic implementations.

Blocks and Procs for Strategy and Command: Ruby's blocks and procs naturally implement Strategy and Command patterns without requiring separate class hierarchies. Any algorithm or request that fits in a callable object becomes a strategy or command.

# Strategy with blocks
class DataProcessor
  def process(data, &strategy)
    strategy.call(data)
  end
end

processor = DataProcessor.new
processor.process([1, 2, 3]) { |data| data.map { |x| x * 2 } }
# => [2, 4, 6]

# Command with procs
class Invoker
  def initialize
    @commands = []
  end
  
  def add_command(&command)
    @commands << command
  end
  
  def execute_all
    @commands.each(&:call)
  end
end

Enumerable for Iterator: Ruby's Enumerable module provides iterator functionality to any class that implements each. This mixin approach eliminates the need for separate iterator classes in most cases.

class CustomCollection
  include Enumerable
  
  def initialize(*items)
    @items = items
  end
  
  def each(&block)
    @items.each(&block)
  end
end

collection = CustomCollection.new(1, 2, 3)
collection.map { |x| x * 2 }
# => [2, 4, 6]

Modules for Template Method: Ruby modules implement template methods through method composition and hook methods. The included and extended hooks enable framework code to execute when modules mix into classes.

module Reportable
  def generate_report
    prepare_data
    format_header
    format_body
    format_footer
  end
  
  def format_header
    "=== Report Header ==="
  end
  
  def format_footer
    "=== End Report ==="
  end
  
  # Classes must implement these
  def prepare_data
    raise NotImplementedError
  end
  
  def format_body
    raise NotImplementedError
  end
end

class SalesReport
  include Reportable
  
  def prepare_data
    @data = {sales: 1000, profit: 200}
  end
  
  def format_body
    "Sales: $#{@data[:sales]}\nProfit: $#{@data[:profit]}"
  end
end

Method Objects for Complex Commands: When commands require state or complex execution logic, Ruby classes as command objects provide clean encapsulation. The call method creates a consistent interface across command types.

class Transaction
  def initialize
    @operations = []
  end
  
  def add(operation)
    @operations << operation
  end
  
  def execute
    @operations.each(&:call)
  end
  
  def rollback
    @operations.reverse.each(&:undo)
  end
end

class CreateRecordOperation
  def initialize(table, data)
    @table = table
    @data = data
    @id = nil
  end
  
  def call
    @id = @table.insert(@data)
  end
  
  def undo
    @table.delete(@id) if @id
  end
end

State with Class-Based State Objects: Ruby's duck typing allows state objects to share a common interface without formal inheritance. State transitions happen by assigning different state objects to an instance variable.

class OrderStateMachine
  attr_reader :state
  
  def initialize
    @state = PendingState.new(self)
  end
  
  def confirm
    @state.confirm
  end
  
  def ship
    @state.ship
  end
  
  def cancel
    @state.cancel
  end
  
  def transition_to(state_class)
    @state = state_class.new(self)
  end
end

class PendingState
  def initialize(context)
    @context = context
  end
  
  def confirm
    @context.transition_to(ConfirmedState)
    "Order confirmed"
  end
  
  def ship
    "Cannot ship pending order"
  end
  
  def cancel
    @context.transition_to(CancelledState)
    "Order cancelled"
  end
end

Observer with Ruby Observable: Ruby's standard library includes Observable module implementing the Observer pattern. Objects extend Observable and call changed and notify_observers to broadcast state changes.

require 'observer'

class StockTicker
  include Observable
  
  attr_reader :price
  
  def initialize(symbol)
    @symbol = symbol
    @price = 0
  end
  
  def update_price(new_price)
    @price = new_price
    changed
    notify_observers(@symbol, @price)
  end
end

class PriceAlert
  def update(symbol, price)
    puts "#{symbol} now at $#{price}"
  end
end

ticker = StockTicker.new("TECH")
alert = PriceAlert.new
ticker.add_observer(alert)
ticker.update_price(150.50)
# => TECH now at $150.50

Method Missing for Dynamic Proxies: Ruby's method_missing enables dynamic proxies that intercept method calls, implementing patterns like Chain of Responsibility or Command without explicit handler/command classes.

class MethodLogger
  def initialize(target)
    @target = target
  end
  
  def method_missing(method, *args, &block)
    puts "Calling #{method} with #{args.inspect}"
    result = @target.send(method, *args, &block)
    puts "Result: #{result.inspect}"
    result
  end
  
  def respond_to_missing?(method, include_private = false)
    @target.respond_to?(method, include_private)
  end
end

logged_array = MethodLogger.new([1, 2, 3])
logged_array.map { |x| x * 2 }
# => Calling map with []
# => Result: [2, 4, 6]

Ruby's dynamic features reduce ceremonial code required for behavioral patterns, but the underlying pattern concepts remain important. Understanding when Ruby idioms implement patterns helps recognize design structures in Ruby codebases and choose between explicit pattern implementations and Ruby's built-in features.

Practical Examples

Behavioral patterns solve real-world coordination and interaction problems. These examples demonstrate pattern application in common scenarios.

Multi-Step Form Validation with Chain of Responsibility: Web applications often require complex validation where multiple validators examine input sequentially. Chain of Responsibility allows adding and ordering validators without coupling the form to specific validation logic.

class ValidationChain
  def initialize
    @validators = []
  end
  
  def add(validator)
    @validators << validator
    self
  end
  
  def validate(data)
    @validators.each do |validator|
      result = validator.validate(data)
      return result unless result.valid?
    end
    ValidationResult.new(true)
  end
end

class ValidationResult
  attr_reader :valid, :errors
  
  def initialize(valid, errors = [])
    @valid = valid
    @errors = errors
  end
  
  def valid?
    @valid
  end
end

class EmailValidator
  def validate(data)
    if data[:email] =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
      ValidationResult.new(true)
    else
      ValidationResult.new(false, ["Invalid email format"])
    end
  end
end

class PasswordValidator
  def validate(data)
    if data[:password].length >= 8
      ValidationResult.new(true)
    else
      ValidationResult.new(false, ["Password too short"])
    end
  end
end

chain = ValidationChain.new
  .add(EmailValidator.new)
  .add(PasswordValidator.new)

result = chain.validate(email: "user@example.com", password: "secret123")

Plugin System with Strategy and Template Method: Applications requiring extensible behavior without core modification combine Strategy for algorithm variation and Template Method for consistent plugin lifecycle.

module Plugin
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def plugin_name(name = nil)
      @plugin_name = name if name
      @plugin_name
    end
  end
  
  def initialize(config = {})
    @config = config
    setup
  end
  
  def execute(context)
    before_execution(context)
    result = perform(context)
    after_execution(context, result)
    result
  end
  
  def setup
    # Subclasses override for initialization
  end
  
  def before_execution(context)
    # Hook for pre-processing
  end
  
  def after_execution(context, result)
    # Hook for post-processing
  end
  
  def perform(context)
    raise NotImplementedError
  end
end

class ImageProcessingPlugin
  include Plugin
  
  plugin_name "Image Processor"
  
  def perform(context)
    image = context[:image]
    strategy = context[:strategy] || :resize
    send(strategy, image)
  end
  
  def resize(image)
    "Resized #{image}"
  end
  
  def compress(image)
    "Compressed #{image}"
  end
end

plugin = ImageProcessingPlugin.new(max_size: 1000)
plugin.execute(image: "photo.jpg", strategy: :compress)
# => "Compressed photo.jpg"

State Machine for Order Processing: E-commerce systems require complex order state management. The State pattern encapsulates state-specific behavior and transitions.

class Order
  attr_reader :items, :total, :state
  
  def initialize(items)
    @items = items
    @total = items.sum { |item| item[:price] * item[:quantity] }
    @state = DraftState.new(self)
  end
  
  def submit
    @state.submit
  end
  
  def pay
    @state.pay
  end
  
  def ship
    @state.ship
  end
  
  def complete
    @state.complete
  end
  
  def cancel
    @state.cancel
  end
  
  def transition_to(new_state_class)
    @state = new_state_class.new(self)
  end
end

class DraftState
  def initialize(order)
    @order = order
  end
  
  def submit
    @order.transition_to(SubmittedState)
    "Order submitted for processing"
  end
  
  def pay
    "Cannot pay draft order. Submit first."
  end
  
  def ship
    "Cannot ship draft order"
  end
  
  def complete
    "Cannot complete draft order"
  end
  
  def cancel
    @order.transition_to(CancelledState)
    "Draft cancelled"
  end
end

class SubmittedState
  def initialize(order)
    @order = order
  end
  
  def submit
    "Order already submitted"
  end
  
  def pay
    @order.transition_to(PaidState)
    "Payment processed: $#{@order.total}"
  end
  
  def ship
    "Cannot ship unpaid order"
  end
  
  def complete
    "Cannot complete unpaid order"
  end
  
  def cancel
    @order.transition_to(CancelledState)
    "Order cancelled with refund"
  end
end

class PaidState
  def initialize(order)
    @order = order
  end
  
  def submit
    "Order already submitted"
  end
  
  def pay
    "Order already paid"
  end
  
  def ship
    @order.transition_to(ShippedState)
    "Order shipped to customer"
  end
  
  def complete
    "Cannot complete unshipped order"
  end
  
  def cancel
    "Cannot cancel paid order. Contact support."
  end
end

Observer for Real-Time Dashboard Updates: Dashboards monitoring system metrics require real-time updates when data changes. Observer pattern decouples metric sources from display components.

class MetricsAggregator
  def initialize
    @observers = []
    @metrics = {}
  end
  
  def add_observer(observer)
    @observers << observer
    observer.update(@metrics)
  end
  
  def remove_observer(observer)
    @observers.delete(observer)
  end
  
  def update_metric(name, value)
    @metrics[name] = value
    notify_observers
  end
  
  private
  
  def notify_observers
    @observers.each { |observer| observer.update(@metrics) }
  end
end

class DashboardWidget
  attr_reader :display_name
  
  def initialize(name, filter)
    @display_name = name
    @filter = filter
    @current_value = nil
  end
  
  def update(metrics)
    new_value = @filter.call(metrics)
    if new_value != @current_value
      @current_value = new_value
      render
    end
  end
  
  def render
    puts "#{@display_name}: #{@current_value}"
  end
end

aggregator = MetricsAggregator.new

cpu_widget = DashboardWidget.new(
  "CPU Usage",
  ->(m) { m[:cpu_percent] }
)

memory_widget = DashboardWidget.new(
  "Memory Available",
  ->(m) { m[:memory_free_mb] }
)

aggregator.add_observer(cpu_widget)
aggregator.add_observer(memory_widget)

aggregator.update_metric(:cpu_percent, 45.2)
# => CPU Usage: 45.2
aggregator.update_metric(:memory_free_mb, 2048)
# => Memory Available: 2048

These examples demonstrate how behavioral patterns solve coordination problems while maintaining loose coupling and code flexibility. Pattern selection depends on the specific interaction structure and whether the problem involves algorithms, requests, state transitions, or notifications.

Design Considerations

Selecting appropriate behavioral patterns requires understanding their strengths, limitations, and when each pattern applies best. Design decisions involve trade-offs between flexibility, complexity, and performance.

Pattern Selection Criteria: Different behavioral patterns address different interaction structures. Observer works for one-to-many notifications where observers remain independent. Mediator handles many-to-many communications where objects must coordinate but shouldn't reference each other directly. Chain of Responsibility processes requests sequentially with early termination. Command encapsulates requests for queuing, logging, or undo. Strategy encapsulates algorithms when multiple implementations exist for the same operation.

The interaction cardinality guides pattern choice. One-to-many relationships suggest Observer. Many-to-many relationships with coordination requirements suggest Mediator. Sequential processing with conditional continuation suggests Chain of Responsibility. State-dependent behavior suggests State pattern. Algorithm variation suggests Strategy.

Flexibility vs Complexity Trade-offs: Behavioral patterns increase flexibility at the cost of additional classes and indirection. Strategy replaces conditional logic with strategy objects, trading simple conditionals for object composition and potential runtime strategy switching. State replaces state conditionals with state objects, enabling complex state transitions but requiring state class management. Chain of Responsibility replaces nested conditionals with handler chains, providing flexibility but obscuring the request path.

The flexibility benefits must justify the added complexity. Applications requiring frequent algorithm changes benefit from Strategy despite extra classes. Systems with complex state machines benefit from State pattern despite state class proliferation. Simple scenarios with stable algorithms rarely need these abstractions.

Performance Implications: Behavioral patterns introduce performance overhead through additional object creation, indirection, and method calls. Observer pattern notification loops scale linearly with observer count. Command pattern object creation adds allocation overhead for each command. Visitor pattern double dispatch requires two virtual method calls per element visit.

Performance costs remain negligible in most applications, but high-frequency operations or real-time systems require measurement. Caching strategy objects, pooling command objects, or using direct method calls instead of patterns may prove necessary in performance-critical paths.

Testing Considerations: Behavioral patterns generally improve testability by isolating variation points. Strategy objects test independently from contexts. Command objects test without executing commands. Observer pattern allows testing subjects and observers separately. Chain of Responsibility enables testing individual handlers without the complete chain.

The improved testability assumes proper dependency injection. Patterns using hard-coded dependencies (new SpecificStrategy inside a context) lose testing benefits. Dependency injection frameworks or manual constructor injection enable test double substitution.

Maintenance and Understandability: Behavioral patterns affect code maintainability in opposing ways. Patterns improve maintainability by encapsulating variation and providing consistent structures. Developers familiar with patterns recognize Observer implementations or Strategy hierarchies immediately. Patterns reduce code duplication by extracting common behavior.

Conversely, patterns add indirection that obscures program flow. Following execution through multiple pattern objects requires understanding pattern mechanics. Overuse creates architecture astronomy where finding concrete behavior requires traversing many abstractions. The maintenance benefit appears when variation points change frequently and pattern structure remains stable.

Ruby Idiom vs Explicit Pattern: Ruby often provides idiomatic alternatives to explicit pattern implementations. Blocks replace simple Strategy and Command objects. Modules replace Template Method in many cases. Enumerable replaces Iterator. The choice between Ruby idioms and explicit patterns depends on pattern complexity and abstraction requirements.

Use Ruby idioms when behavior remains simple and pattern structure provides no benefit. Use explicit patterns when behavior requires state management, lifecycle control, or formal abstraction boundaries. The pattern makes sense if removing it increases coupling or reduces clarity.

# Simple case - Ruby idiom
collection.sort_by { |item| item.priority }

# Complex case - Explicit strategy
class PriorityStrategy
  def initialize(weights)
    @weights = weights
  end
  
  def compare(a, b)
    score_a = calculate_score(a)
    score_b = calculate_score(b)
    score_b <=> score_a
  end
  
  def calculate_score(item)
    @weights[:urgency] * item.urgency +
    @weights[:importance] * item.importance +
    @weights[:effort] * (1.0 / item.effort)
  end
end

Design decisions balance flexibility needs against complexity costs while considering Ruby's expressive capabilities. Patterns prove valuable when variation points change independently, multiple implementations coexist, or formal abstraction boundaries improve clarity.

Common Pitfalls

Behavioral pattern implementations encounter recurring problems that reduce their effectiveness or introduce bugs. Recognizing these pitfalls helps avoid them.

Observer Memory Leaks: Observer implementations that fail to remove observers create memory leaks. Observers hold references preventing garbage collection even when observers should become unreferenced.

# Memory leak - observer never removed
class Subject
  def initialize
    @observers = []
  end
  
  def add_observer(observer)
    @observers << observer
  end
  
  def notify
    @observers.each { |o| o.update(self) }
  end
end

subject = Subject.new
1000.times do
  observer = Observer.new
  subject.add_observer(observer)
  # Observer should be garbage collected but subject keeps reference
end

Solution: Implement remove_observer and ensure observers unregister when finished. Use weak references for automatic cleanup, though Ruby lacks built-in weak reference support in older versions.

Command Pattern Memory Explosion: Systems queuing commands without bounds accumulate commands indefinitely. Undo systems keeping all command history consume memory proportional to action count.

# Unbounded command history
class UndoManager
  def initialize
    @history = []
  end
  
  def execute(command)
    command.execute
    @history << command  # Grows without limit
  end
  
  def undo
    command = @history.pop
    command.undo
  end
end

Solution: Implement history limits, discard old commands, or use command compression. Track memory usage and prune history when exceeding thresholds.

Strategy Explosion with Combinatorial Strategies: Systems with multiple variation dimensions create combinatorial strategy explosions. A report system with format strategies and delivery strategies requires separate strategy classes for each combination if improperly designed.

# Strategy explosion
class PDFEmailReport < Report
  # Combines PDF format and email delivery
end

class PDFPrintReport < Report
  # Combines PDF format and print delivery
end

class HTMLEmailReport < Report
  # Combines HTML format and email delivery
end
# Requires N * M classes for N formats and M deliveries

Solution: Use composition with multiple strategy dimensions rather than single strategy hierarchies. Separate format strategy from delivery strategy.

class Report
  def initialize(format_strategy, delivery_strategy)
    @format = format_strategy
    @delivery = delivery_strategy
  end
  
  def generate
    content = @format.format(data)
    @delivery.deliver(content)
  end
end

State Pattern with Shared State: State objects that share mutable state between state instances create race conditions and bugs. Each state object might modify shared data assuming exclusive access.

Solution: Store all mutable data in the context object, not in state objects. State objects should remain stateless except for the context reference. Multiple state instances should safely coexist.

Chain of Responsibility Without Termination: Handler chains that fail to terminate cause requests to fall through without processing. No error occurs but the request never gets handled.

class HandlerChain
  def initialize
    @handlers = []
  end
  
  def handle(request)
    @handlers.each do |handler|
      result = handler.handle(request)
      return result if result  # Terminates on first success
    end
    nil  # Request unhandled - silent failure
  end
end

Solution: Raise exceptions for unhandled requests or return explicit unhandled indicators. Implement default handlers as chain terminators.

Visitor with Unstable Object Structures: Visitor pattern becomes maintenance burden when element hierarchies change frequently. Each new element type requires updating all visitor classes with new visit methods.

Solution: Avoid Visitor for frequently changing structures. Consider alternative approaches like type-based dispatch tables or dynamic dispatch through method_missing. Use Visitor only for stable element hierarchies with many operations.

Template Method Fragility: Template methods with many hook methods create fragile base classes. Subclasses must implement numerous methods correctly, and changes to the template algorithm break subclasses.

Solution: Minimize required hook methods. Provide sensible defaults for optional hooks. Document the template algorithm and hook responsibilities clearly. Consider Strategy instead of Template Method when variation points outnumber commonalities.

Mediator Becoming God Object: Mediators that control too many components and handle too many interaction types become monolithic God Objects. The mediator concentrates all coordination logic, becoming a maintenance bottleneck.

# Mediator doing too much
class ApplicationMediator
  def handle_user_action(action)
    # Coordinates UI, business logic, persistence, notifications...
  end
  
  def handle_data_change(data)
    # Updates views, caches, indexes, logs...
  end
  
  def handle_system_event(event)
    # Manages timers, background jobs, webhooks...
  end
end

Solution: Split mediators by subsystem or concern. Create separate mediators for UI coordination, business logic coordination, and infrastructure coordination. Each mediator should manage a cohesive set of interactions.

Command Interface Inconsistency: Command objects with inconsistent interfaces (some use execute, others run, others call) reduce pattern effectiveness. Commands should follow uniform interfaces for generic handling.

Solution: Standardize on single method names. Ruby convention favors call for command-like objects, matching proc and lambda behavior. Implement to_proc to enable command objects as blocks.

Avoiding these pitfalls requires attention to pattern responsibilities, resource management, and system boundaries. Patterns should simplify rather than complicate, and implementations should match actual complexity requirements rather than adding speculative flexibility.

Reference

Behavioral Pattern Catalog

Pattern Intent Key Participants Applicability
Chain of Responsibility Decouple request sender from receiver by chaining handlers Handler, Concrete Handlers, Client Processing requests with multiple potential handlers, middleware stacks, event bubbling
Command Encapsulate requests as objects Command, Concrete Commands, Invoker, Receiver Undo/redo, request queuing, transaction logging, macro recording
Iterator Provide sequential access without exposing representation Iterator, Concrete Iterator, Aggregate Traversing collections, custom iteration logic, multiple traversal algorithms
Mediator Encapsulate object interactions Mediator, Concrete Mediator, Colleagues Complex object interactions, reducing coupling between components, centralizing control logic
Memento Capture and restore object state Memento, Originator, Caretaker Undo mechanisms, snapshots, checkpointing, transaction rollback
Observer Define one-to-many dependency for state notifications Subject, Concrete Subject, Observer, Concrete Observers Event handling, publish-subscribe, model-view separation, reactive updates
State Alter behavior based on internal state State, Concrete States, Context State-dependent behavior, state machines, protocol implementations
Strategy Encapsulate interchangeable algorithms Strategy, Concrete Strategies, Context Algorithm variation, policy selection, configurable behavior
Template Method Define algorithm skeleton, defer steps to subclasses Abstract Class, Concrete Classes Invariant algorithm structure with variable steps, framework hooks
Visitor Define operations on elements without changing element classes Visitor, Concrete Visitors, Element, Concrete Elements Operations on stable hierarchies, double dispatch, AST processing

Pattern Relationships

Pattern Pair Relationship Notes
Strategy vs State State transitions automatically, Strategy requires explicit switching State manages temporal progression, Strategy encapsulates algorithm choice
Command vs Strategy Command encapsulates requests, Strategy encapsulates algorithms Commands often contain receiver and parameters, strategies contain computation
Observer vs Mediator Observer distributes changes, Mediator centralizes communication Observer for one-to-many, Mediator for many-to-many coordination
Chain of Responsibility vs Command Chain processes request, Command encapsulates request Can combine: chain of command handlers
Template Method vs Strategy Template Method uses inheritance, Strategy uses composition Template Method for stable structures, Strategy for runtime variation
Iterator vs Visitor Iterator accesses elements, Visitor operates on elements Often combined: iterator provides access, visitor provides operation

Ruby Implementation Idioms

Pattern Ruby Idiom When to Use Explicit Pattern
Strategy Blocks and procs Complex strategies with state, multiple strategy methods, strategy reuse across contexts
Command Blocks and procs Command lifecycle management, undo/redo, command serialization, complex parameter handling
Iterator Enumerable module Custom iteration state, external iterators, bidirectional iteration
Template Method Module mixins with hooks Multiple hook methods, complex lifecycle, framework extension points
Observer Observable module or pub-sub gems Complex notification logic, observer filtering, asynchronous notifications
State Direct conditional logic for simple cases Complex state transitions, many states, state-specific behavior
Chain of Responsibility Array of callables with find or each Handler lifecycle, dynamic chain modification, handler statistics

Common Method Names

Pattern Standard Methods Ruby Conventions
Strategy execute, do_algorithm call (matches Proc interface)
Command execute, undo, redo call, undo, redo
State handle, enter, exit State-specific action methods, enter_state, exit_state
Observer update, notify update (Observable convention)
Visitor visit, accept visit_element_type, accept
Chain of Responsibility handle, handle_request call, handle

Pattern Selection Decision Tree

Question Yes Path No Path
Does behavior depend on object state that changes frequently? Consider State pattern Continue to next question
Do you need to encapsulate multiple algorithms for the same operation? Consider Strategy pattern Continue to next question
Do you need to notify multiple objects about state changes? Consider Observer pattern Continue to next question
Must multiple objects coordinate without direct references? Consider Mediator pattern Continue to next question
Do you need to process requests with multiple potential handlers? Consider Chain of Responsibility Continue to next question
Must you capture and restore object state? Consider Memento pattern Continue to next question
Do you need to queue, log, or undo requests? Consider Command pattern Continue to next question
Will you define operations on stable element hierarchies? Consider Visitor pattern Consider simpler approaches

Memory Management Considerations

Pattern Memory Risk Mitigation Strategy
Observer Unbounded observer lists, memory leaks Implement remove_observer, use weak references, automatic cleanup on garbage collection
Command Command history accumulation Implement history limits, command compression, periodic pruning
Memento State snapshot accumulation Limit snapshot count, implement expiration, compress snapshots
Chain of Responsibility Long chains with state Keep handlers stateless, limit chain length, monitor memory usage
Mediator Centralized component references Use weak references for optional components, implement cleanup
State State object proliferation with state data Keep state objects stateless, share immutable state objects, store mutable data in context