BEHAVIORAL DESIGN PATTERNS
1. Chain of Responsibility
Chain of Responsibility is a behavioral design pattern that lets you pass
requests along a chain of handlers. Upon receiving a request, each handler
decides either to process the request or to pass it to the next handler in the chain.
Use:
Use the Chain of Responsibility pattern when your program is expected to
process different kinds of requests in various ways, but the exact types of
requests and their sequences are unknown beforehand.
Use the pattern when it’s essential to execute several handlers in a particular
order.
Use the CoR pattern when the set of handlers and their order are supposed to
change at runtime.
Example: A leave approval system.
Description:
Employee → Team Lead → Manager → Director
If the Team Lead cannot approve more than 3 days, the request is forwarded to
the superior.
abstract class Approver {
protected Approver successor;
public void SetSuccessor(Approver successor) => this.successor = successor;
public abstract void HandleRequest(int days);
}
class TeamLead : Approver {
public override void HandleRequest(int days) {
if (days <= 3) Console.WriteLine("Approved by TeamLead");
else successor?.HandleRequest(days);
}
}
class Manager : Approver {
public override void HandleRequest(int days) {
if (days <= 7) Console.WriteLine("Approved by Manager");
else successor?.HandleRequest(days);
}
}
2. Command
Command is a behavioral design pattern that turns a request into a stand-
alone object that contains all information about the request. This transformation
lets you pass requests as a method arguments, delay or queue a request’s
execution, and support undoable operations.
Use:
Use the Command pattern when you want to parametrize objects with
operations.
Use the Command pattern when you want to queue operations, schedule
their execution, or execute them remotely.
Use the Command pattern when you want to implement reversible
operations.
Example: A remote control with buttons to turn lights on/off.
Description:
The remote (invoker) calls command.Execute(), and the Light is the receiver.
interface ICommand {
void Execute();
}
class Light {
public void TurnOn() => Console.WriteLine("Light ON");
}
class LightOnCommand : ICommand {
private Light light;
public LightOnCommand(Light l) => light = l;
public void Execute() => light.TurnOn();
}
3. Iterator
Iterator is a behavioral design pattern that lets you traverse elements of a
collection without exposing its underlying representation (list, stack, tree, etc.).
Use:
Use the Iterator pattern when your collection has a complex data structure
under the hood, but you want to hide its complexity from clients (either for
convenience or security reasons).
Use the pattern to reduce duplication of the traversal code across your app.
Use the Iterator when you want your code to be able to traverse different
data structures or when types of these structures are unknown beforehand.
Example: Iterating through a playlist of songs.
List<string> songs = new List<string> { "Song1", "Song2" };
foreach (string song in songs) {
Console.WriteLine(song); // Iterator pattern in action
}
4. Mediator
Mediator (Also known as: Intermediary, Controller) is a behavioral design
pattern that lets you reduce chaotic dependencies between objects. The pattern
restricts direct communications between the objects and forces them to
collaborate only via a mediator object.
Use:
Use the Mediator pattern when it’s hard to change some of the classes
because they are tightly coupled to a bunch of other classes.
Use the pattern when you can’t reuse a component in a different program
because it’s too dependent on other components.
Use the Mediator when you find yourself creating tons of component
subclasses just to reuse some basic behavior in various contexts.
Example: A group chat system where users communicate via a server.
interface IChatMediator {
void SendMessage(string msg, User user);
}
class ChatRoom : IChatMediator {
private List<User> users = new List<User>();
public void AddUser(User user) => users.Add(user);
public void SendMessage(string msg, User user) {
foreach (var u in users)
if (u != user) u.Receive(msg);
}
}
class User {
private string name;
private IChatMediator mediator;
public User(string name, IChatMediator m) {
this.name = name; mediator = m;
}
public void Send(string msg) => mediator.SendMessage(msg, this);
public void Receive(string msg) => Console.WriteLine(name + " got: " +
msg);
}
5. Mememto
Memento (Also known as: Snapshot) is a behavioral design pattern that lets
you save and restore the previous state of an object without revealing the
details of its implementation
Use:
Use the Memento pattern when you want to produce snapshots of the
object’s state to be able to restore a previous state of the object.
Use the pattern when direct access to the object’s fields/getters/setters
violates its encapsulation.
Example: Undo functionality in a text editor.
class Editor {
public string Text { get; set; }
public Memento Save() => new Memento(Text);
public void Restore(Memento m) => Text = m.State;
}
class Memento {
public string State { get; private set; }
public Memento(string state) => State = state;
}
6. Observer
Observer (Also known as: Event-Subscriber, Listener) is a behavioral
design pattern that lets you define a subscription mechanism to notify multiple
objects about any events that happen to the object they’re observing.
Use:
Use the Observer pattern when changes to the state of one object may
require changing other objects, and the actual set of objects is unknown
beforehand or changes dynamically.
Use the pattern when some objects in your app must observe others, but only
for a limited time or in specific cases.
Example: When a new product arrives, notify all subscribed customers.
interface IObserver { void Update(); }
class Customer : IObserver {
private string name;
public Customer(string name) => this.name = name;
public void Update() => Console.WriteLine(name + " was notified");
}
class Store {
private List<IObserver> customers = new List<IObserver>();
public void AddObserver(IObserver o) => customers.Add(o);
public void NewProductArrived() {
Console.WriteLine("New product arrived!");
customers.ForEach(c => c.Update());
}
}
7. State
State is a behavioral design pattern that lets an object alter its behavior when
its internal state changes. It appears as if the object changed its class.
Use:
Use the State pattern when you have an object that behaves differently
depending on its current state, the number of states is enormous, and the state-
specific code changes frequently.
Use the pattern when you have a class polluted with massive conditionals
that alter how the class behaves according to the current values of the class’s
fields.
Use State when you have a lot of duplicate code across similar states and
transitions of a condition-based state machine.
Example: An ATM with states: Ready, No Cash, Processing.
interface IState { void Handle(); }
class ReadyState : IState {
public void Handle() => Console.WriteLine("ATM is ready.");
}
class NoCashState : IState {
public void Handle() => Console.WriteLine("ATM has no cash.");
}
class ATM {
private IState currentState;
public void SetState(IState state) => currentState = state;
public void Request() => currentState.Handle();
}
8. Strategy
Strategy is a behavioral design pattern that lets you define a family of
algorithms, put each of them into a separate class, and make their objects
interchangeable.
Use:
Use the Strategy pattern when you want to use different variants of an
algorithm within an object and be able to switch from one algorithm to another
during runtime.
Use the Strategy when you have a lot of similar classes that only differ in the
way they execute some behavior.
Use the pattern to isolate the business logic of a class from the
implementation details of algorithms that may not be as important in the
context of that logic.
Use the pattern when your class has a massive conditional statement that
switches between different variants of the same algorithm.
Example: Choosing a shipping cost algorithm: by weight, distance, or service.
interface IShippingStrategy {
double Calculate(double weight);
}
class StandardShipping : IShippingStrategy {
public double Calculate(double weight) => weight * 5;
}
class ExpressShipping : IShippingStrategy {
public double Calculate(double weight) => weight * 10;
}
class Order {
private IShippingStrategy strategy;
public Order(IShippingStrategy s) => strategy = s;
public void PrintShippingCost(double w) =>
Console.WriteLine(strategy.Calculate(w));
}
9. Template Method
Template Method is a behavioral design pattern that defines the skeleton of
an algorithm in the superclass but lets subclasses override specific steps of the
algorithm without changing its structure.
Use:
Use the Template Method pattern when you want to let clients extend only
particular steps of an algorithm, but not the whole algorithm or its structure.
Use the pattern when you have several classes that contain almost identical
algorithms with some minor differences. As a result, you might need to modify
all classes when the algorithm changes.
Example: Preparing a beverage: boil water → brew → add toppings → pour.
abstract class Beverage {
public void PrepareRecipe() {
BoilWater();
Brew();
Pour();
AddCondiments();
}
void BoilWater() => Console.WriteLine("Boiling water");
protected abstract void Brew();
void Pour() => Console.WriteLine("Pouring into cup");
protected abstract void AddCondiments();
}
class Coffee : Beverage {
protected override void Brew() => Console.WriteLine("Dripping coffee");
protected override void AddCondiments() => Console.WriteLine("Adding
sugar");
}
10. Visitor
Visitor is a behavioral design pattern that lets you separate algorithms from
the objects on which they operate.
Use:
Use the Visitor when you need to perform an operation on all elements of a
complex object structure (for example, an object tree).
Use the Visitor to clean up the business logic of auxiliary behaviors.
Use the pattern when a behavior makes sense only in some classes of a class
hierarchy, but not in others.
Example: Calculating tax on different types of products.
interface IElement { void Accept(IVisitor visitor); }
class Book : IElement {
public double Price => 20;
public void Accept(IVisitor visitor) => visitor.Visit(this);
}
interface IVisitor {
void Visit(Book b);
}
class TaxVisitor : IVisitor {
public void Visit(Book b) => Console.WriteLine("Tax on book: " + b.Price *
0.1);
}