Behavioral Patterns

20 min read

Overview

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. They describe not just patterns of objects or classes but also the patterns of communication between them. These patterns characterize complex control flow that is difficult to follow at run-time; they shift focus away from flow of control to let you concentrate on the way objects are interconnected.

Behavioral class patterns use inheritance to distribute behavior between classes. Two appear here. Template Method is the simpler and more common: it gives an abstract, step-by-step definition of an algorithm, where each step invokes an abstract or primitive operation that a subclass fleshes out. Interpreter represents a grammar as a class hierarchy and implements an interpreter as an operation on instances of these classes.

Behavioral object patterns use object composition instead of inheritance. Some describe how peer objects cooperate to perform a task no single object can carry out alone; the key issue is how peers know about each other without excessive coupling. Mediator introduces an intermediary between peers; Chain of Responsibility couples them even more loosely by passing a request along a chain of candidate handlers. Observer maintains a one-to-many dependency between objects. Other object patterns encapsulate behavior in an object and delegate to it: Strategy encapsulates an algorithm, Command a request, State a state-dependent behavior, Visitor behavior otherwise distributed across classes, and Iterator the way you access and traverse the elements of an aggregate.

Key takeaways

Mental model

Mental model

Chain of Responsibility

Intent

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. (Object Behavioral.)

Applicability

Use Chain of Responsibility when more than one object may handle a request and the handler is not known a priori — the handler should be ascertained automatically; when you want to issue a request to one of several objects without specifying the receiver explicitly; or when the set of objects that can handle a request should be specified dynamically.

Structure and participants

The motivating example is a context-sensitive help facility: a help request is handled by one of several user-interface objects, organized from most specific to most general. The object that ultimately provides the help is not known explicitly to the object (the button) that initiates the request — the request has an implicit receiver.

  • Handler (HelpHandler) — defines an interface for handling requests; optionally implements the successor link.
  • ConcreteHandler (PrintButton, PrintDialog) — handles requests it is responsible for and can access its successor; if it can handle the request it does so, otherwise it forwards the request to its successor.
  • Client — initiates the request to a ConcreteHandler object on the chain.

When a client issues a request, the request propagates along the chain until a ConcreteHandler object takes responsibility for handling it.

Consequences

  1. Reduced coupling. An object only has to know that a request will be handled "appropriately"; neither receiver nor sender has explicit knowledge of the other, and an object in the chain need not know the chain's structure. Instead of references to all candidate receivers, each object keeps a single reference to its successor.
  2. Added flexibility in assigning responsibilities. You can add or change responsibilities by changing the chain at run-time, and combine this with subclassing to specialize handlers statically.
  3. Receipt isn't guaranteed. Since a request has no explicit receiver, there is no guarantee it will be handled — it can fall off the end of the chain, or go unhandled if the chain is misconfigured.

A common implementation maintains the successor link in the Handler and gives HandleRequest a default that forwards unconditionally, so an uninterested ConcreteHandler need not override it. Related: often applied with Composite, where a component's parent acts as its successor.

class HelpHandler {
public:
  HelpHandler(HelpHandler* s = 0) : _successor(s) { }
  virtual void HandleHelp() {
    if (_successor) _successor->HandleHelp();
  }
private:
  HelpHandler* _successor;
};

Command

Intent

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. Also known as Action, Transaction. (Object Behavioral.)

Applicability

Use Command to parameterize objects by an action to perform (an object-oriented replacement for callbacks); to specify, queue, and execute requests at different times (a Command can outlive the original request); to support undo (Execute stores state for reversal, and an added Unexecute reverses it, with executed commands kept on a history list); to support logging changes so they can be reapplied after a crash; and to structure a system around high-level operations built on primitive operations, as transactions do.

Structure and participants

The key is an abstract Command class declaring an Execute operation. Concrete subclasses bind a receiver-action pair, storing the receiver and implementing Execute to invoke the request. A Menu item configured with a concrete Command calls Execute when selected without knowing which subclass it uses.

  • Command — declares an interface for executing an operation.
  • ConcreteCommand (PasteCommand, OpenCommand) — defines a binding between a Receiver and an action; implements Execute by invoking operations on Receiver.
  • Client (Application) — creates a ConcreteCommand and sets its receiver.
  • Invoker (MenuItem) — asks the command to carry out the request.
  • Receiver (Document, Application) — knows how to perform the operations. Any class may serve as a Receiver.

Consequences

  1. Command decouples the object that invokes the operation from the one that knows how to perform it.
  2. Commands are first-class objects — manipulated and extended like any other object.
  3. You can assemble commands into a composite command (a MacroCommand), an instance of the Composite pattern.
  4. It is easy to add new Commands without changing existing classes.

For undo, a ConcreteCommand may need to store the Receiver, the arguments, and original values; one level of undo needs only the last command, while multi-level undo/redo needs a history list traversed backward (Unexecute) and forward (Execute). A command whose state varies per invocation must be copied before going on the history list — such commands act as Prototype. The Memento pattern can give a command access to other objects' state for undo without exposing their internals.

class Command {
public:
  virtual ~Command();
  virtual void Execute() = 0;
protected:
  Command();
};

class MacroCommand : public Command {
public:
  virtual void Execute() {
    ListIterator<Command*> i(_cmds);
    for (i.First(); !i.IsDone(); i.Next())
      i.CurrentItem()->Execute();
  }
private:
  List<Command*>* _cmds;
};

Interpreter

Intent

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language. (Class Behavioral.)

Applicability

Use Interpreter when there is a language to interpret and you can represent statements as abstract syntax trees. It works best when the grammar is simple — for complex grammars the class hierarchy becomes large and unmanageable, and parser generators are a better alternative — and when efficiency is not a critical concern.

Structure and participants

The pattern uses a class to represent each grammar rule; symbols on the right-hand side become instance variables. A regular-expression grammar yields an abstract RegularExpression and subclasses LiteralExpression, AlternationExpression, SequenceExpression, and RepetitionExpression. Every expression is represented by an abstract syntax tree of these instances.

  • AbstractExpression (RegularExpression) — declares an abstract Interpret operation common to all nodes.
  • TerminalExpression (LiteralExpression) — implements Interpret for terminal symbols; one instance per terminal symbol in a sentence.
  • NonterminalExpression (AlternationExpression, RepetitionExpression, SequenceExpression) — one class per rule of the form R produces R1 R2 ... Rn; holds AbstractExpression variables for R1 through Rn and implements Interpret, typically recursing on them.
  • Context — holds information global to the interpreter.
  • Client — builds (or is given) the abstract syntax tree and invokes Interpret.

Consequences

  1. It is easy to change and extend the grammar, since inheritance over the rule classes lets you modify and extend expressions.
  2. Implementing the grammar is easy too — the node classes have similar, easily generated implementations.
  3. Complex grammars are hard to maintain, because the pattern defines at least one class per rule.
  4. Adding new ways to interpret expressions is easy — define a new operation on the expression classes; if you keep adding interpretations, use Visitor to avoid changing the grammar classes.

The pattern does not address parsing — the syntax tree may be built by a table-driven or recursive-descent parser, or by the client. The Flyweight pattern can share terminal symbols, since terminal nodes carry no position information and receive context during interpretation. Related: the syntax tree is a Composite, an Iterator can traverse it, and Visitor can hold the per-node behavior in one class.

class BooleanExp {
public:
  virtual bool Evaluate(Context&) = 0;
  virtual BooleanExp* Replace(const char*, BooleanExp&) = 0;
  virtual BooleanExp* Copy() const = 0;
};

bool AndExp::Evaluate(Context& c) {
  return _operand1->Evaluate(c) && _operand2->Evaluate(c);
}

Iterator

Intent

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. Also known as Cursor. (Object Behavioral.)

Applicability

Use Iterator to access an aggregate's contents without exposing its internal representation; to support multiple traversals of aggregate objects; and to provide a uniform interface for traversing different aggregate structures (polymorphic iteration).

Structure and participants

The key idea is to take responsibility for access and traversal out of the list and put it into an iterator object, which keeps track of the current element. A List calls for a ListIterator with operations First, Next, IsDone, and CurrentItem. To support different aggregates uniformly, an abstract Iterator and an abstract Aggregate are defined, and the aggregate creates its iterator via a factory method CreateIterator (an instance of Factory Method).

  • Iterator — defines an interface for accessing and traversing elements.
  • ConcreteIterator — implements the Iterator interface and keeps track of the current position.
  • Aggregate — defines an interface for creating an Iterator object.
  • ConcreteAggregate — implements iterator creation to return the proper ConcreteIterator.

Consequences

  1. It supports variations in the traversal of an aggregate — just replace the iterator instance, or define new Iterator subclasses.
  2. Iterators simplify the Aggregate interface, obviating a traversal interface there.
  3. More than one traversal can be pending on an aggregate, since each iterator keeps its own state.

Implementation notes

Key choices: who controls the iteration — an external iterator (client advances and requests elements) is more flexible, while an internal iterator (the iterator applies a client-supplied operation to each element) is easier to use; who defines the traversal algorithm — if the aggregate defines it and the iterator only stores position, the iterator is a cursor, a simple form of Memento; how robust the iterator is to insertions and removals during traversal; additional operations such as Previous and SkipTo; and, in C++, a cleanup Proxy (a stack-allocated IteratorPtr) that deletes a heap-allocated polymorphic iterator in its destructor. A NullIterator (always done) helps traverse tree structures like Composite uniformly. Related: Memento can capture an iteration's state.

template <class Item>
class Iterator {
public:
  virtual void First() = 0;
  virtual void Next() = 0;
  virtual bool IsDone() const = 0;
  virtual Item CurrentItem() const = 0;
};

Mediator

Intent

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently. (Object Behavioral.)

Applicability

Use Mediator when a set of objects communicate in well-defined but complex ways, producing unstructured and hard-to-understand interdependencies; when reusing an object is difficult because it refers to and communicates with many others; and when a behavior distributed between several classes should be customizable without a lot of subclassing.

Structure and participants

In a dialog box, widgets have many interdependencies (a button disables when an entry field is empty; selecting a list-box entry changes an entry field). Encapsulating this collective behavior in a separate mediator — a FontDialogDirector — makes it the hub of communication; widgets know only the director, reducing interconnections.

  • Mediator (DialogDirector) — defines an interface for communicating with Colleague objects.
  • ConcreteMediator (FontDialogDirector) — implements cooperative behavior by coordinating Colleagues; knows and maintains its colleagues.
  • Colleague classes (ListBox, EntryField) — each knows its Mediator and communicates with it whenever it would otherwise communicate with another colleague.

Colleagues send and receive requests from the Mediator, which routes requests between the appropriate colleagues.

Consequences

  1. It limits subclassing — changing interaction behavior requires subclassing Mediator only; Colleagues are reused as is.
  2. It decouples colleagues — vary and reuse Colleague and Mediator classes independently.
  3. It simplifies object protocols — replacing many-to-many with one-to-many interactions, which are easier to understand and extend.
  4. It abstracts how objects cooperate — making mediation an independent concept clarifies object interaction.
  5. It centralizes control — trading interaction complexity for complexity in the mediator, which can itself become a hard-to-maintain monolith.

Colleague-mediator communication may itself use the Observer pattern (colleagues as Subjects), or a specialized notification interface where a colleague passes itself as an argument. Facade differs from Mediator: Facade abstracts a subsystem with a unidirectional protocol, whereas Mediator enables multidirectional cooperative behavior.

void FontDialogDirector::WidgetChanged(Widget* changed) {
  if (changed == _fontList) {
    _fontName->SetText(_fontList->GetSelection());
  }
}

Memento

Intent

Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. Also known as Token. (Object Behavioral.)

Applicability

Use Memento when a snapshot of (some portion of) an object's state must be saved so it can be restored later, and a direct interface to obtaining that state would expose implementation details and break the object's encapsulation.

Structure and participants

A memento stores a snapshot of another object — its originator. The undo mechanism requests a memento when it needs to checkpoint state; the originator initializes it with information characterizing its current state. The memento is "opaque" to other objects — only the originator stores and retrieves its contents.

  • Memento (SolverState) — stores the Originator's internal state; protects against access by objects other than the originator. Mementos have two interfaces: Caretaker sees a narrow interface (it can only pass the memento on), while Originator sees a wide interface (full access to restore itself).
  • Originator (ConstraintSolver) — creates a memento with a snapshot of its state and uses a memento to restore its state.
  • Caretaker (undo mechanism) — keeps the memento safe and never operates on or examines its contents.

A caretaker requests a memento, holds it, and passes it back to the originator. Mementos are passive — only the originator that created one assigns or retrieves its state.

Consequences

  1. Preserving encapsulation boundaries — Memento shields other objects from complex Originator internals.
  2. It simplifies Originator — clients manage the state they request rather than burdening the Originator.
  3. Using mementos might be expensive — if the Originator must copy large state, or clients create mementos often.
  4. Defining narrow and wide interfaces can be hard in some languages.
  5. Hidden costs in caring for mementos — a caretaker may incur large storage costs without knowing how much state a memento holds.

In C++, the wide/narrow split is achieved by making Originator a friend of Memento with Memento's wide interface private and only the narrow interface public. When mementos are created and restored in a predictable order, they can store only the incremental change. Related: Command uses mementos for undoable operations; Iterator can use mementos to capture iteration state.

class Memento {
public:
  virtual ~Memento();
private:
  friend class Originator;
  Memento();
  void SetState(State*);
  State* GetState();
};

Observer

Intent

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Also known as Dependents, Publish-Subscribe. (Object Behavioral.)

Applicability

Use Observer when an abstraction has two aspects, one dependent on the other, and you want to vary and reuse them independently; when a change to one object requires changing others and you do not know how many; and when an object should notify other objects without making assumptions about who they are (avoiding tight coupling).

Structure and participants

The key objects are subject and observer. A subject may have any number of dependent observers, all notified whenever it changes state; each observer then queries the subject to synchronize. This is publish-subscribe: the subject publishes notifications without knowing who its observers are.

  • Subject — knows its observers and provides an interface to attach and detach Observer objects.
  • Observer — defines an updating interface for objects to be notified of changes in a subject.
  • ConcreteSubject — stores state of interest and notifies observers when it changes.
  • ConcreteObserver — keeps a reference to a ConcreteSubject, stores state that should stay consistent, and implements the updating interface.

Consequences

  1. Abstract coupling between Subject and Observer — the subject knows only that it has a list of observers conforming to the abstract interface, so they may belong to different layers.
  2. Support for broadcast communication — notifications need not specify a receiver; observers are free to be added and removed at any time.
  3. Unexpected updates — an innocuous operation may cause a cascade of updates, and the simple protocol carries no detail about what changed.

Implementation notes

Key choices: mapping subjects to observers (explicit references vs. an associative lookup); observing more than one subject (pass the subject as a parameter to Update); who triggers the update — state-setting operations calling Notify, or clients calling it after a batch of changes; avoiding dangling references to deleted subjects; ensuring Subject state is self-consistent before Notify (use a Template Method with Notify last); the push vs pull models for how much information the subject sends; registering interest in specific events; and encapsulating complex update semantics in a ChangeManager — itself an instance of Mediator, often a Singleton.

void Subject::Notify() {
  ListIterator<Observer*> i(_observers);
  for (i.First(); !i.IsDone(); i.Next())
    i.CurrentItem()->Update(this);
}

State

Intent

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. Also known as Objects for States. (Object Behavioral.)

Applicability

Use State when an object's behavior depends on its state and it must change behavior at run-time depending on that state; and when operations have large, multipart conditional statements that depend on the object's state. The State pattern puts each branch of the conditional into a separate class, treating the state as an object that can vary independently.

Structure and participants

For a TCPConnection that can be Established, Listening, or Closed, an abstract TCPState declares the common interface, and subclasses (TCPEstablished, TCPListen, TCPClosed) implement state-specific behavior. TCPConnection keeps a state object and delegates all state-specific requests to it; when the connection changes state, it swaps the state object.

  • Context (TCPConnection) — defines the client interface and maintains an instance of a ConcreteState subclass defining the current state.
  • State (TCPState) — defines an interface for the behavior associated with a particular state of the Context.
  • ConcreteState subclasses (TCPEstablished, TCPListen, TCPClosed) — each implements behavior associated with a state of the Context.

Context delegates state-specific requests to the current ConcreteState object, optionally passing itself so the State can access it. Either Context or the ConcreteState subclasses can decide which state succeeds another.

Consequences

  1. It localizes state-specific behavior and partitions behavior for different states — replacing scattered look-alike conditionals with one object per state.
  2. It makes state transitions explicit, and protects the Context from inconsistent states (transitions are atomic — rebinding one variable).
  3. State objects can be shared — if they have no instance variables, they are essentially stateless Flyweights.

Decentralizing transition logic into the State subclasses is flexible but introduces dependencies between subclasses. A table-based alternative maps inputs to transitions but is harder to extend with actions. State objects can be created lazily or kept permanently. Related: Flyweight explains sharing; State objects are often Singletons.

class TCPState {
public:
  virtual void Open(TCPConnection*);
  virtual void Close(TCPConnection*);
  virtual void Transmit(TCPConnection*, TCPOctetStream*);
protected:
  void ChangeState(TCPConnection*, TCPState*);
};

Strategy

Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. Also known as Policy. (Object Behavioral.)

Applicability

Use Strategy when many related classes differ only in their behavior; when you need different variants of an algorithm (for example, different space/time trade-offs); when an algorithm uses data clients should not know about; and when a class defines many behaviors that appear as multiple conditional statements — move the branches into their own Strategy classes.

Structure and participants

A Composition delegates linebreaking to a Compositor object; subclasses SimpleCompositor, TeXCompositor, and ArrayCompositor implement different strategies. The client installs the desired Compositor into the Composition.

  • Strategy (Compositor) — declares an interface common to all supported algorithms; Context calls the algorithm through it.
  • ConcreteStrategy (SimpleCompositor, TeXCompositor, ArrayCompositor) — implements the algorithm using the Strategy interface.
  • Context (Composition) — is configured with a ConcreteStrategy, keeps a reference to it, and may define an interface letting the Strategy access its data.

A Context forwards requests from its clients to its strategy; clients usually create and pass the ConcreteStrategy, then interact only with the context.

Consequences

  1. Families of related algorithms — hierarchies of Strategy classes factor out common functionality.
  2. An alternative to subclassing — varying behavior without hard-wiring it into Context, and supporting run-time variation.
  3. Strategies eliminate conditional statements — many conditionals selecting behavior often signal the need for Strategy.
  4. A choice of implementations — different time/space trade-offs of the same behavior.
  5. Clients must be aware of different Strategies — they must understand how strategies differ to choose one.
  6. Communication overhead — the shared interface may pass information a simple strategy never uses.
  7. Increased number of objects — reducible by making strategies stateless, shareable Flyweights.

Strategy and Template Method solve the same problem differently: Template Method varies steps of an algorithm via inheritance; Strategy varies the whole algorithm via delegation. In C++, a Strategy can also be supplied as a template parameter when it is fixed at compile-time. Strategy objects often make good flyweights.

class Compositor {
public:
  virtual int Compose(
    Coord natural[], Coord stretch[], Coord shrink[],
    int componentCount, int lineWidth, int breaks[]) = 0;
protected:
  Compositor();
};

Template Method

Intent

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. (Class Behavioral.)

Applicability

Use Template Method to implement the invariant parts of an algorithm once and leave the varying behavior to subclasses; when common behavior among subclasses should be factored and localized in a common class to avoid duplication ("refactoring to generalize"); and to control subclass extensions by calling "hook" operations only at specific points.

Structure and participants

An abstract Application defines OpenDocument as a template method that fixes the steps — CanOpenDocument, DoCreateDocument, AboutToOpenDocument, and DoRead — while letting subclasses supply the varying steps.

  • AbstractClass (Application) — defines abstract primitive operations that concrete subclasses implement, and implements a template method defining the algorithm skeleton.
  • ConcreteClass (MyApplication) — implements the primitive operations to carry out subclass-specific steps.

ConcreteClass relies on AbstractClass to implement the invariant steps.

Consequences

Template methods are a fundamental technique for code reuse, especially in class libraries. They lead to an inverted control structure — the "Hollywood principle," don't call us, we'll call you — where the parent class calls subclass operations. Template methods call concrete operations, concrete AbstractClass operations, primitive (abstract) operations, Factory Methods, and hook operations that provide default behavior subclasses may extend. It is important to specify which operations are hooks (may be overridden) and which are abstract (must be overridden).

In C++, primitive operations are typically protected (callable only by the template method), those that must be overridden are pure virtual, and the template method itself is non-virtual so it cannot be overridden. Related: Factory Methods are often called by template methods; Strategy varies the whole algorithm via delegation where Template Method varies steps via inheritance.

void View::Display() {
  SetFocus();
  DoDisplay();
  ResetFocus();
}
void View::DoDisplay() { }   // hook: subclasses override

Visitor

Intent

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (Object Behavioral.)

Applicability

Use Visitor when an object structure contains many classes of objects with differing interfaces and you want to perform operations that depend on their concrete classes; when many distinct, unrelated operations must be performed on the structure and you want to avoid polluting the element classes with them; and when the classes defining the object structure rarely change but you often define new operations over the structure.

Structure and participants

A compiler's abstract syntax tree needs many operations — type-checking, code generation, pretty-printing. Rather than distribute these across node classes, package related operations into a visitor passed to elements as the tree is traversed. An element "accepts" the visitor and calls back the operation encoding the element's class (VisitAssignment, VisitVariableReference).

  • Visitor (NodeVisitor) — declares a Visit operation for each class of ConcreteElement; the operation's signature identifies the element's class.
  • ConcreteVisitor (TypeCheckingVisitor) — implements each operation, providing the algorithm fragment for the corresponding element class and storing local state accumulated during traversal.
  • Element (Node) — defines an Accept operation that takes a visitor as an argument.
  • ConcreteElement (AssignmentNode, VariableRefNode) — implements Accept by calling the matching Visit operation on the visitor.
  • ObjectStructure (Program) — enumerates its elements; may be a Composite or a collection.

Consequences

  1. Visitor makes adding new operations easy — add a new visitor instead of changing every element class.
  2. A visitor gathers related operations and separates unrelated ones — behavior is localized in visitor subclasses.
  3. Adding new ConcreteElement classes is hard — each new element forces a new operation on Visitor and every ConcreteVisitor.
  4. Visiting across class hierarchies — unlike an Iterator, a Visitor can visit objects without a common parent class.
  5. Accumulating state — visitors can accumulate state during traversal instead of passing extra arguments.
  6. Breaking encapsulation — Visitor may force elements to expose internal state through public operations.

Visitor achieves its effect through double-dispatch: the operation executed depends on both the Visitor's type and the Element's type. Accept binds at run-time, so extending the element interface means defining one new Visitor subclass rather than many element subclasses. Traversal responsibility can sit in the object structure, in an Iterator, or in the visitor itself. Related: applies over a Composite, and Interpreter may use Visitor to do the interpretation.

void AssignmentNode::Accept(NodeVisitor& v) {
  v.VisitAssignment(this);
}
void VariableRefNode::Accept(NodeVisitor& v) {
  v.VisitVariableRef(this);
}

Encapsulating variation

Encapsulating variation is a theme of many behavioral patterns. When an aspect of a program changes frequently, these patterns define an object that encapsulates that aspect, and other parts of the program collaborate with it whenever they depend on that aspect. A Strategy object encapsulates an algorithm; a State object encapsulates state-dependent behavior; a Mediator object encapsulates the protocol between objects; and an Iterator object encapsulates the way you access and traverse the components of an aggregate. Were it not for the pattern, the new object's functionality would usually be wired directly into the existing object that uses it.

Continue exploring

Tags