Behavioral design patterns describe recurring ways to assign responsibilities, select algorithms, manage state, pass requests, traverse collections, and coordinate communication among objects and classes. Their shared concern is not simply what a system contains, but how its participants collaborate while the program is running.
The central lesson of this module is that interaction logic deserves deliberate design. When changing behavior is scattered across conditionals, callbacks, peer references, and control code, even a structurally sound class model can become difficult to extend. A behavioral pattern gives that variation a recognizable form and clarifies which participant owns each decision.
Most behavioral patterns separate a stable collaboration from some aspect that varies. Strategy encapsulates interchangeable algorithms. State represents behavior associated with an object's current state. Command turns a request into an object. Observer defines a subscription and notification relationship. Mediator centralizes coordination policy that would otherwise be distributed among colleagues. The other behavioral patterns address similarly specific interaction problems.
This separation can reduce coupling, improve testability, and make intended extension points easier to identify. These benefits are not automatic. A pattern improves a design only when its participants, responsibilities, and consequences match the problem. Applying a pattern to simple behavior can add indirection without adding useful flexibility.
Behavioral patterns also change how developers reason about control flow. Instead of following one long procedure, a developer may need to trace a request through a chain, a notification to several observers, or an event from colleagues through a mediator. Clear interfaces, focused participant names, tests, and diagnostic logging help make these distributed interactions observable.
The Observer lessons developed a one-to-many collaboration between a subject and a changeable set of dependents. Observers register through a common contract, and a state change causes the subject to notify them. The subject knows that listeners exist without knowing concrete charts, displays, audit components, or other responses. A new observer can join without requiring existing observers to change.
This structure suits editors with several views of one model, event-driven interfaces, and monitoring systems. It also clarifies Observer's relationship to Model-View-Controller. A model can act as the subject, views observe model changes, and controllers translate user actions into model operations. Observer supplies notification; MVC assigns the broader presentation responsibilities.
The Java implementation made registration, removal, and notification explicit and distinguished push from pull. Push carries changed data or an event to the observer. Pull announces a change and lets the observer query the subject. Push may send unneeded data, while pull increases knowledge of the subject's query interface. Event types or separate subscription channels can limit irrelevant notifications.
Loose coupling does not remove operational responsibilities. A design must define mutation during notification, duplicate registration, exception handling, and synchronous or asynchronous dispatch. Snapshot iteration can make mutation predictable, while exception boundaries can isolate failures. Multithreaded publishers need a concurrency policy, and long-lived subjects must not retain obsolete observers.
Modern Java code should avoid the deprecated java.util.Observable and java.util.Observer APIs. A domain-specific listener is often clearest in-process. Reactive streams that require subscription control and backpressure can use java.util.concurrent.Flow. Its protocol shares Observer's intent but is more demanding than a callback list.
The Mediator lessons addressed a different interaction problem. When several peer objects store references to one another and distribute collaboration rules across their classes, the resulting dependency graph becomes difficult to understand and change. A mediator gives those objects, called colleagues in the GoF description, one communication point. Each colleague reports meaningful events or requests through the mediator; the concrete mediator decides which colleagues should respond and in what order.
The structure is flexible. A mediator interface defines the communication contract when multiple implementations or independent testing justify it. A concrete mediator owns coordination policy and the colleague references it needs. Colleagues depend on the mediator abstraction rather than on peers. An abstract colleague superclass is optional; domain names and narrow interfaces are usually clearer than an artificial hierarchy.
Mediator reduces direct peer coupling, centralizes interaction rules, and can make colleagues easier to reuse and test. The complexity has not disappeared; it has moved into the concrete mediator. That move is valuable when the mediator represents one cohesive collaboration, such as a dialog, workflow, conversation, or intersection. It becomes harmful when unrelated domain calculations, persistence, presentation, infrastructure, and error recovery accumulate in one god object. Focused collaborators, typed messages, explicit state transitions, and multiple bounded mediators help keep coordination understandable.
The traffic-flow course project applied these ideas to four directional signals. Each signal requests a change without inspecting or commanding another signal. The mediator owns the FIFO request queue, rejects duplicate requests, enforces the minimum green interval, and changes the active signal to red before granting green to the next eligible direction. Deterministic simulated time makes those decisions repeatable in tests. The example demonstrates Mediator because the coordinator applies real cross-object policy, not because it merely forwards method calls.
The teaching model also illustrates responsible scoping. A production controller would require compatible movement groups, clearance phases, pedestrian handling, sensors, emergency policy, hardware interlocks, diagnostics, and fail-safe behavior. Those requirements do not all belong inside one mediator. Mediator locates collaboration decisions; it does not supply concurrency, distributed consistency, or physical safety.
The Gang of Four distinguishes behavioral class patterns from behavioral object patterns by their primary reuse mechanism. Behavioral class patterns use inheritance to distribute behavior between classes. Interpreter and Template Method belong to this group. Template Method fixes the outline of an algorithm in a base class while allowing subclasses to redefine selected steps. Interpreter represents a grammar and uses its class structure to interpret expressions in that language.
Behavioral object patterns primarily use object composition and delegation. Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, and Visitor belong to this group. Their participating objects can often be connected or replaced at runtime, although every implementation remains subject to the contracts and invariants of its collaboration.
This classification identifies the dominant mechanism, not an absolute restriction. A Strategy implementation may use inheritance internally, and a Template Method may call composed collaborators. The practical question is where variation is placed and whether inheritance or composition produces the clearer dependency structure for the specific design.
| Pattern | Primary design problem |
|---|---|
| Chain of Responsibility | Passes a request through potential handlers until an appropriate handler processes it. |
| Command | Encapsulates a request as an object so it can be queued, logged, parameterized, or undone. |
| Interpreter | Represents a simple grammar and defines how expressions in that language are interpreted. |
| Iterator | Traverses a collection without exposing its internal representation. |
| Mediator | Encapsulates coordination among colleagues so they do not require direct knowledge of one another. |
| Memento | Captures and restores an object's internal state without exposing that state inappropriately. |
| Observer | Notifies subscribed dependents when a subject or publisher changes or emits an event. |
| State | Lets an object vary its behavior when its internal state changes. |
| Strategy | Encapsulates interchangeable algorithms behind a common contract. |
| Template Method | Defines an algorithm's overall sequence while allowing subclasses to customize selected steps. |
| Visitor | Adds operations across a stable object structure without placing every operation in the element classes. |
Several patterns can appear similar because they may produce comparable class diagrams or use related implementation techniques. Intent distinguishes them. State and Strategy can both delegate to a polymorphic object, but State models behavior driven by internal state transitions, whereas Strategy represents a selected algorithm or policy. Observer distributes notifications to subscribers, while Mediator coordinates a bounded collaboration and may direct different actions to different colleagues. Command represents an operation, while Chain of Responsibility determines which handler receives a request.
Begin with the design pressure, not a pattern name. Identify what changes, which object currently knows too much, and which dependency makes a requirement difficult to implement. Then choose the smallest collaboration that resolves that pressure.
After selecting a candidate, evaluate its consequences. Ask whether the pattern removes a real source of change, whether participants retain cohesive responsibilities, and whether the added abstractions are easier to understand than the original code. Also consider lifecycle, error handling, concurrency, ordering, reentrancy, and performance where the collaboration crosses threads or system boundaries.
Patterns can be combined when their intents remain distinct. A graphical command may be stored in a history for undo, selected through a Strategy, and announced to Observers after execution. A Mediator may receive Commands from colleagues. Such combinations are useful when each pattern solves an independent problem; stacking patterns without a specific reason makes the design harder to trace.
Design patterns complement object-oriented design methods; they do not replace requirements analysis, domain modeling, architecture, or testing. Analysis identifies the problem-domain concepts and behavior that the system must support. Design then assigns software responsibilities and adapts the model to the programming language, frameworks, quality requirements, and expected sources of change.
A reusable implementation model often contains objects that do not appear in the analysis model. Commands, strategies, mediators, iterators, and other design participants may be introduced to manage software responsibilities rather than represent real-world entities. Patterns provide a vocabulary for explaining why these participants exist and how they collaborate.
UML and other notations can show the resulting structure and interactions, but a diagram alone does not capture the design reasoning. A useful pattern description includes the problem, context, forces, participants, collaboration, consequences, and known alternatives. This information records the theory behind a design instead of presenting only its final class arrangement.
Use behavioral patterns as tested design options, not mandatory templates. First make the current behavior and likely changes explicit. Prefer straightforward code while the problem is straightforward. Introduce a pattern when it gives a recurring interaction problem a clearer ownership boundary, safer extension point, or more testable collaboration.
Finally, review the implementation rather than judging it by pattern names. Confirm that responsibilities are not duplicated, dependencies point in the intended direction, public contracts express the collaboration clearly, and failure paths are covered by tests. A well-applied behavioral pattern makes the reason for an interaction easier to explain. A poorly applied one merely gives unnecessary complexity a familiar name.
The next module moves from individual pattern mechanics to broader questions of pattern-oriented design: how to compare candidate patterns, combine compatible patterns, and choose a design that fits the actual forces of a software problem.