06 Dec 2023
Behavioral patterns are a type of software design pattern that focuses on the communication and interaction between objects within a system. They provide solutions for common communication patterns between objects, such as how objects can share information, how they can respond to events, and how they can coordinate their actions. Behavioral patterns are concerned with the algorithms and the assignment of responsibilities between objects.
Let's explore each behavioral design pattern with its definition, main components, and C# code implementation along with explanations.
1. Observer Pattern:
Definition:
Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Main Components:
- Subject: Maintains a list of observers, and notifies them of state changes.
- Observer: Defines an interface for updating when the subject's state changes.
- ConcreteSubject: Implements the
Subjectinterface and maintains state. - ConcreteObserver: Implements the
Observerinterface to update its state when notified by theSubject.
C# Code Implementation:
// Subject
public interface ISubject
{
void AddObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers();
}
// Observer
public interface IObserver
{
void Update(string message);
}
// Concrete Subject
public class ConcreteSubject : ISubject
{
private List<IObserver> observers = new List<IObserver>();
private string state;
public void AddObserver(IObserver observer)
{
observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in observers)
{
observer.Update(state);
}
}
public void SetState(string newState)
{
state = newState;
NotifyObservers();
}
}
// Concrete Observer
public class ConcreteObserver : IObserver
{
private readonly string name;
public ConcreteObserver(string name)
{
this.name = name;
}
public void Update(string message)
{
Console.WriteLine($"{name} received update: {message}");
}
}
// Client
public class Client
{
public void UseObserver()
{
var subject = new ConcreteSubject();
var observer1 = new ConcreteObserver("Observer 1");
var observer2 = new ConcreteObserver("Observer 2");
subject.AddObserver(observer1);
subject.AddObserver(observer2);
subject.SetState("New State");
}
}
// Usage
var client = new Client();
client.UseObserver();
Explanation:
The Observer Pattern establishes a one-to-many relationship between a Subject and multiple Observers. The ConcreteSubject maintains a list of observers and notifies them when its state changes. ConcreteObserver implements the Observer interface to receive updates from the Subject. The Client sets up the relationship by adding observers to the subject and triggering a state change.
2. Strategy Pattern:
Definition:
Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the client choose the algorithm at runtime.
Main Components:
- Context: Maintains a reference to the strategy object and may define an interface that the strategies must adhere to.
- Strategy: Defines an interface or abstract class common to all supported algorithms.
- ConcreteStrategy: Implements a specific algorithm.
C# Code Implementation:
// Strategy
public interface IStrategy
{
void Execute();
}
// Concrete Strategy A
public class ConcreteStrategyA : IStrategy
{
public void Execute()
{
Console.WriteLine("Executing Strategy A");
}
}
// Concrete Strategy B
public class ConcreteStrategyB : IStrategy
{
public void Execute()
{
Console.WriteLine("Executing Strategy B");
}
}
// Context
public class Context
{
private IStrategy strategy;
public Context(IStrategy strategy)
{
this.strategy = strategy;
}
public void SetStrategy(IStrategy strategy)
{
this.strategy = strategy;
}
public void ExecuteStrategy()
{
strategy.Execute();
}
}
// Client
public class Client
{
public void UseStrategy()
{
var context = new Context(new ConcreteStrategyA());
context.ExecuteStrategy();
context.SetStrategy(new ConcreteStrategyB());
context.ExecuteStrategy();
}
}
// Usage
var client = new Client();
client.UseStrategy();
Explanation:
The Strategy Pattern defines a family of algorithms (IStrategy), encapsulates each algorithm in a concrete strategy class (ConcreteStrategyA, ConcreteStrategyB), and allows them to be interchangeable. The Context class maintains a reference to the current strategy and delegates the algorithm's execution to the strategy. The Client can switch between different strategies at runtime.
3. Command Pattern:
Definition:
Command Pattern encapsulates a request as an object, thereby allowing for parameterization of clients with different requests, queuing of requests, and logging of the parameters.
Main Components:
- Command: Declares an interface for executing a particular operation.
- ConcreteCommand: Defines a binding between a
Receiverobject and an action. - Receiver: Knows how to perform the operation.
- Invoker: Asks the command to execute the request.
C# Code Implementation:
// Command
public interface ICommand
{
void Execute();
}
// Concrete Command
public class ConcreteCommand : ICommand
{
private readonly Receiver receiver;
public ConcreteCommand(Receiver receiver)
{
this.receiver = receiver;
}
public void Execute()
{
receiver.Action();
}
}
// Receiver
public class Receiver
{
public void Action()
{
Console.WriteLine("Receiver Action");
}
}
// Invoker
public class Invoker
{
private ICommand command;
public void SetCommand(ICommand command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
// Client
public class Client
{
public void UseCommand()
{
var receiver = new Receiver();
var command = new ConcreteCommand(receiver);
var invoker = new Invoker();
invoker.SetCommand(command);
invoker.ExecuteCommand();
}
}
// Usage
var client = new Client();
client.UseCommand();
Explanation:
The Command Pattern encapsulates a request (ICommand) as an object. The ConcreteCommand class binds a specific operation to a Receiver object. The Receiver knows how to perform the operation. The Invoker asks the command to execute the request. The Client creates an instance of the Receiver, a ConcreteCommand with that receiver, and an Invoker to execute the command.
4. Template Method Pattern:
Definition:
Template Method Pattern defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
Main Components:
- AbstractClass: Declares the template method containing the steps of the algorithm.
- ConcreteClass: Implements the steps of the algorithm that are specific to the subclass.
C# Code Implementation:
// Abstract Class
public abstract class AbstractClass
{
public void TemplateMethod()
{
CommonStep1();
SpecificStep();
CommonStep2();
}
protected void CommonStep1()
{
Console.WriteLine("Common Step 1");
}
protected void CommonStep2()
{
Console.WriteLine("Common Step 2");
}
protected abstract void SpecificStep();
}
// Concrete Class A
public class
ConcreteClassA : AbstractClass
{
protected override void SpecificStep()
{
Console.WriteLine("Concrete Class A - Specific Step");
}
}
// Concrete Class B
public class ConcreteClassB : AbstractClass
{
protected override void SpecificStep()
{
Console.WriteLine("Concrete Class B - Specific Step");
}
}
// Client
public class Client
{
public void UseTemplateMethod(AbstractClass template)
{
template.TemplateMethod();
}
}
// Usage
var client = new Client();
var concreteA = new ConcreteClassA();
var concreteB = new ConcreteClassB();
client.UseTemplateMethod(concreteA);
client.UseTemplateMethod(concreteB);
Explanation:
The Template Method Pattern defines the skeleton of an algorithm in the AbstractClass. The algorithm consists of common steps (CommonStep1 and CommonStep2) and a specific step (SpecificStep) that is left for the subclasses (ConcreteClassA, ConcreteClassB) to implement. The Client can use the template method with different subclasses, allowing for flexibility in the implementation of specific steps.
5. State Pattern:
Definition:
State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
Main Components:
- Context: Maintains an instance of a
Statesubclass and delegates state-specific behavior to that instance. - State: Defines an interface for encapsulating the behavior associated with a particular state.
- ConcreteState: Implements the behavior associated with a specific state.
C# Code Implementation:
// State
public interface IState
{
void Handle(Context context);
}
// Concrete State A
public class ConcreteStateA : IState
{
public void Handle(Context context)
{
Console.WriteLine("Handling State A");
context.SetState(new ConcreteStateB());
}
}
// Concrete State B
public class ConcreteStateB : IState
{
public void Handle(Context context)
{
Console.WriteLine("Handling State B");
context.SetState(new ConcreteStateA());
}
}
// Context
public class Context
{
private IState state;
public Context()
{
state = new ConcreteStateA();
}
public void SetState(IState newState)
{
state = newState;
}
public void Request()
{
state.Handle(this);
}
}
// Client
public class Client
{
public void UseState()
{
var context = new Context();
context.Request();
context.Request();
context.Request();
}
}
// Usage
var client = new Client();
client.UseState();
Explanation:
The State Pattern allows an object (Context) to alter its behavior by changing its internal state, represented by different State subclasses (ConcreteStateA, ConcreteStateB). The Context maintains an instance of a State and delegates state-specific behavior to that instance. The Client can use the Context to make requests, and the behavior changes based on the current state.
6. Chain of Responsibility Pattern:
Definition:
Chain of Responsibility Pattern passes a request 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.
Main Components:
- Handler: Defines an interface for handling requests and optionally implements the successor link.
- ConcreteHandler: Implements the handling of requests and can access its successor.
- Client: Initiates the request to a handler.
C# Code Implementation:
// Handler
public abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
public abstract void HandleRequest(int request);
}
// Concrete Handler A
public class ConcreteHandlerA : Handler
{
public override void HandleRequest(int request)
{
if (request < 10)
{
Console.WriteLine("Handled by Concrete Handler A");
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
// Concrete Handler B
public class ConcreteHandlerB : Handler
{
public override void HandleRequest(int request)
{
if (request >= 10 && request < 20)
{
Console.WriteLine("Handled by Concrete Handler B");
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
// Client
public class Client
{
public void UseChainOfResponsibility()
{
var handlerA = new ConcreteHandlerA();
var handlerB = new ConcreteHandlerB();
handlerA.SetSuccessor(handlerB);
handlerA.HandleRequest(5);
handlerA.HandleRequest(15);
handlerA.HandleRequest(25);
}
}
// Usage
var client = new Client();
client.UseChainOfResponsibility();
Explanation:
The Chain of Responsibility Pattern passes a request along a chain of handlers (Handler, ConcreteHandlerA, ConcreteHandlerB). Each handler decides whether to handle the request or pass it to the next handler in the chain. The Client initiates the request to the first handler in the chain. In this example, ConcreteHandlerA handles requests less than 10 and passes others to its successor, ConcreteHandlerB, which handles requests between 10 and 20.
Certainly! Let's explore each behavioral design pattern with its definition, main components, and C# code implementation along with explanations.
7. Iterator Pattern:
Definition:
Iterator Pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Main Components:
- Iterator: Defines an interface for accessing and traversing elements.
- ConcreteIterator: Implements the
Iteratorinterface and keeps track of the current position in the traversal. - Aggregate: Defines an interface for creating an
Iteratorobject. - ConcreteAggregate: Implements the
Aggregateinterface and creates anIteratorfor its elements.
C# Code Implementation:
// Iterator
public interface IIterator<T>
{
T Current();
bool MoveNext();
void Reset();
}
// Concrete Iterator
public class ConcreteIterator<T> : IIterator<T>
{
private readonly ConcreteAggregate<T> aggregate;
private int index = 0;
public ConcreteIterator(ConcreteAggregate<T> aggregate)
{
this.aggregate = aggregate;
}
public T Current()
{
return aggregate.GetElement(index);
}
public bool MoveNext()
{
index++;
return index < aggregate.Count();
}
public void Reset()
{
index = 0;
}
}
// Aggregate
public interface IAggregate<T>
{
IIterator<T> CreateIterator();
}
// Concrete Aggregate
public class ConcreteAggregate<T> : IAggregate<T>
{
private readonly List<T> elements = new List<T>();
public void AddElement(T element)
{
elements.Add(element);
}
public IIterator<T> CreateIterator()
{
return new ConcreteIterator<T>(this);
}
public T GetElement(int index)
{
return elements[index];
}
public int Count()
{
return elements.Count;
}
}
// Client
public class Client
{
public void UseIterator()
{
var aggregate = new ConcreteAggregate<int>();
aggregate.AddElement(1);
aggregate.AddElement(2);
aggregate.AddElement(3);
var iterator = aggregate.CreateIterator();
while (iterator.MoveNext())
{
Console.WriteLine(iterator.Current());
}
}
}
// Usage
var client = new Client();
client.UseIterator();
Explanation:
The Iterator Pattern provides a way to traverse elements of an aggregate object without exposing its underlying structure. The IIterator interface defines methods for accessing elements (Current), moving to the next element (MoveNext), and resetting the iterator (Reset). The ConcreteIterator keeps track of the current position in the traversal. The IAggregate interface declares a method for creating an iterator (CreateIterator). The ConcreteAggregate maintains a list of elements and creates an iterator for traversal. The Client uses the iterator to iterate over the elements of the aggregate.
8. Visitor Pattern:
Definition:
Visitor Pattern represents an operation to be performed on the elements of an object structure. It lets you define a new operation without changing the classes of the elements on which it operates.
Main Components:
- Visitor: Declares a visit operation for each class of concrete element in the object structure.
- ConcreteVisitor: Implements each operation declared by the
Visitorfor the corresponding concrete element classes. - Element: Defines an
Acceptmethod that accepts a visitor. - ConcreteElement: Implements the
Elementinterface and provides an implementation for theAcceptmethod. - ObjectStructure: Represents an object structure and maintains a collection of elements.
C# Code Implementation:
// Visitor
public interface IVisitor
{
void VisitConcreteElementA(ConcreteElementA elementA);
void VisitConcreteElementB(ConcreteElementB elementB);
}
// Concrete Visitor
public class ConcreteVisitor : IVisitor
{
public void VisitConcreteElementA(ConcreteElementA elementA)
{
Console.WriteLine($"Visitor is processing {elementA.GetType().Name}");
}
public void VisitConcreteElementB(ConcreteElementB elementB)
{
Console.WriteLine($"Visitor is processing {elementB.GetType().Name}");
}
}
// Element
public interface IElement
{
void Accept(IVisitor visitor);
}
// Concrete Element A
public class ConcreteElementA : IElement
{
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteElementA(this);
}
}
// Concrete Element B
public class ConcreteElementB : IElement
{
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteElementB(this);
}
}
// Object Structure
public class ObjectStructure
{
private readonly List<IElement> elements = new List<IElement>();
public void Attach(IElement element)
{
elements.Add(element);
}
public void Detach(IElement element)
{
elements.Remove(element);
}
public void Accept(IVisitor visitor)
{
foreach (var element in elements)
{
element.Accept(visitor);
}
}
}
// Client
public class Client
{
public void UseVisitor()
{
var objectStructure = new ObjectStructure();
objectStructure.Attach(new ConcreteElementA());
objectStructure.Attach(new ConcreteElementB());
var visitor = new ConcreteVisitor();
objectStructure.Accept(visitor);
}
}
// Usage
var client = new Client();
client.UseVisitor();
Explanation:
The Visitor Pattern allows defining a new operation (IVisitor) without changing the classes of the elements (ConcreteElementA, ConcreteElementB) on which it operates. The IVisitor interface declares visit methods for each concrete element class. The ConcreteVisitor implements these visit methods. The IElement interface declares an Accept method that takes a visitor as an argument. The ConcreteElementA and ConcreteElementB classes implement the Accept method, invoking the corresponding visit method on the visitor. The ObjectStructure maintains a collection of elements and allows a visitor to visit each element. The Client creates an object structure, attaches elements, creates a visitor, and lets the visitor visit the elements.
9. Memento Pattern:
Definition:
Memento Pattern captures and externalizes an object's internal state so that the object can be restored to this state later.
Main Components:
- Memento: Stores the internal state of an object.
- Originator: Creates a memento containing a snapshot of its internal state and can restore its state from a memento.
- Caretaker: Holds a memento and is responsible for the safekeeping of the memento.
C# Code Implementation:
// Memento
public class Memento
{
public string State { get; private set; }
public Memento(string state)
{
State = state;
}
}
// Originator
public class Originator
{
private string state;
public void SetState(string state)
{
this.state = state;
}
public Memento CreateMemento()
{
return new Memento(state);
}
public void RestoreFromMemento(Memento memento)
{
state = memento.State;
}
public void DisplayState()
{
Console.WriteLine($"Current State: {state
}");
}
}
// Caretaker
public class Caretaker
{
public Memento Memento { get; set; }
}
// Client
public class Client
{
public void UseMemento()
{
var originator = new Originator();
var caretaker = new Caretaker();
originator.SetState("State 1");
originator.DisplayState();
caretaker.Memento = originator.CreateMemento();
originator.SetState("State 2");
originator.DisplayState();
originator.RestoreFromMemento(caretaker.Memento);
originator.DisplayState();
}
}
// Usage
var client = new Client();
client.UseMemento();
Explanation:
The Memento Pattern allows capturing and externalizing an object's internal state. The Memento class stores the internal state of the Originator. The Originator class creates a memento containing a snapshot of its internal state and can restore its state from a memento. The Caretaker class holds a memento and is responsible for the safekeeping of the memento. The Client creates an originator, sets its state, creates a memento, changes the state, restores the state from the memento, and displays the current state.
10. Interpreter Pattern:
Definition:
Interpreter Pattern defines a grammar for interpreting the sentences in a language and provides an interpreter for the grammar.
Main Components:
- AbstractExpression: Declares an abstract
Interpretoperation that is common to all concrete expressions. - TerminalExpression: Implements the
Interpretoperation for terminal expressions in the grammar. - NonterminalExpression: Represents a rule in the grammar and implements the
Interpretoperation for nonterminal expressions. - Context: Contains information that is global to the interpreter.
C# Code Implementation:
// Abstract Expression
public abstract class AbstractExpression
{
public abstract void Interpret(Context context);
}
// Terminal Expression
public class TerminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
Console.WriteLine("Terminal Expression interpreted");
}
}
// Nonterminal Expression
public class NonterminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
Console.WriteLine("Nonterminal Expression interpreted");
}
}
// Context
public class Context
{
// Contains information that is global to the interpreter
}
// Client
public class Client
{
public void UseInterpreter()
{
var context = new Context();
var expressions = new AbstractExpression[] { new TerminalExpression(), new NonterminalExpression() };
foreach (var expression in expressions)
{
expression.Interpret(context);
}
}
}
// Usage
var client = new Client();
client.UseInterpreter();
Explanation:
The Interpreter Pattern defines a grammar for interpreting sentences in a language. The AbstractExpression declares an abstract Interpret operation that is common to all concrete expressions (TerminalExpression, NonterminalExpression). The TerminalExpression and NonterminalExpression classes implement the Interpret operation for terminal and nonterminal expressions, respectively. The Context class contains information that is global to the interpreter. The Client creates a context and an array of expressions, then iterates over the expressions, interpreting each one.
11. Mediator Pattern:
Definition:
Mediator Pattern defines an object that centralizes communication between a set of objects, making them independent of each other.
Main Components:
- Mediator: Defines an interface for communicating with colleague objects.
- ConcreteMediator: Implements the mediator interface and coordinates communication between colleague objects.
- Colleague: Defines an interface for communication with other colleagues.
- ConcreteColleague: Implements the colleague interface and communicates with other colleagues through the mediator.
C# Code Implementation:
// Mediator
public interface IMediator
{
void SendMessage(Colleague colleague, string message);
}
// Concrete Mediator
public class ConcreteMediator : IMediator
{
private ConcreteColleagueA colleagueA;
private ConcreteColleagueB colleagueB;
public void SetColleagueA(ConcreteColleagueA colleagueA)
{
this.colleagueA = colleagueA;
}
public void SetColleagueB(ConcreteColleagueB colleagueB)
{
this.colleagueB = colleagueB;
}
public void SendMessage(Colleague colleague, string message)
{
if (colleague == colleagueA)
{
colleagueB.ReceiveMessage(message);
}
else if (colleague == colleagueB)
{
colleagueA.ReceiveMessage(message);
}
}
}
// Colleague
public abstract class Colleague
{
protected IMediator mediator;
public Colleague(IMediator mediator)
{
this.mediator = mediator;
}
public abstract void SendMessage(string message);
public abstract void ReceiveMessage(string message);
}
// Concrete Colleague A
public class ConcreteColleagueA : Colleague
{
public ConcreteColleagueA(IMediator mediator) : base(mediator) { }
public override void SendMessage(string message)
{
mediator.SendMessage(this, message);
}
public override void ReceiveMessage(string message)
{
Console.WriteLine($"Colleague A received message: {message}");
}
}
// Concrete Colleague B
public class ConcreteColleagueB : Colleague
{
public ConcreteColleagueB(IMediator mediator) : base(mediator) { }
public override void SendMessage(string message)
{
mediator.SendMessage(this, message);
}
public override void ReceiveMessage(string message)
{
Console.WriteLine($"Colleague B received message: {message}");
}
}
// Client
public class Client
{
public void UseMediator()
{
var mediator = new ConcreteMediator();
var colleagueA = new ConcreteColleagueA(mediator);
var colleagueB = new ConcreteColleagueB(mediator);
mediator.SetColleagueA(colleagueA);
mediator.SetColleagueB(colleagueB);
colleagueA.SendMessage("Hello from Colleague A");
colleagueB.SendMessage("Hi from Colleague B");
}
}
// Usage
var client = new Client();
client.UseMediator();
Explanation:
The Mediator Pattern defines an object (Mediator) that centralizes communication between a set of objects (Colleague), making them independent of each other. The IMediator interface declares a method for sending messages between colleagues. The ConcreteMediator implements this interface and coordinates communication between ConcreteColleagueA and ConcreteColleagueB. The Colleague abstract class defines methods for sending and receiving messages, and the ConcreteColleagueA and ConcreteColleagueB classes implement these methods. The Client sets up the mediator, colleagues, and allows colleagues to communicate through the mediator.
These behavioral design patterns provide solutions for various problems related to object communication, state management, and language interpretation. Understanding their principles and implementations can help in designing flexible and maintainable software systems.