Software Design Patterns
Florian Rappl, Fakultät für Physik, Universität Regensburg
Software Design Patterns
Introduction to modern software architecture
Behavioral patterns
Introduction
- Deal with communication between objects
- Try to identify common communication
- Intentions for implementing such patterns:
- Increased flexibility
- Determined program flow
- Optimize resource usage
- Sometimes patterns introduced here are already included in languages
The observer pattern
- How to notify an unknown amount of unknown objects with a specific message?
- The solution to this question is the observer pattern
- An object, called subject, is maintaining a list of dependents
- They are called observers and contain a known method
- This method is invoked once the state changes
- The invocation is usually called firing, with the message being an event
Observer diagram
Remarks
- Key part of MVC (Model View Controller) architecture (introduced later)
- First implemented in Smalltalk, which introduced MVC and OOP in general and was used to create the first OS with a GUI
- Almost all GUI frameworks contain this pattern
- C# has it inbuilt using the
event
keyword - In Java
java.util.Observable
containsObservable
- This pattern enables de-coupled messaging
Anonymous mute observer
public class Subject
{
private List<IObserver> _observers;
private string _state;
public Subject()
{
_observers = new List<IObserver>();
}
public string State
{
get { return _state; }
set
{
_state = value;
Notify();
}
}
public void Attach(IObserver o)
{
_observers.Add(o);
}
public void Detach(IObserver o)
{
_observers.Remove(o);
}
protected void Notify()
{
foreach (IObserver o in _observers)
{
o.Update(this);
}
}
}
public interface IObserver
{
void Update(object sender);
}
public class ConcreteObserver : IObserver
{
public void Update(object sender)
{
/* Do something with the sender */
}
}
public class Subject {
private List<Observer> _observers;
private string _state;
public Subject() {
_observers = new List<Observer>();
}
public string getState() {
return _state;
}
public void setState(string value) {
_state = value;
Notify();
}
public void attach(Observer o) {
_observers.add(o);
}
public void detach(Observer o) {
_observers.remove(o);
}
protected void notify() {
for (Observer o : _observers) {
o.update(this);
}
}
}
public interface Observer {
void update(object sender);
}
public class ConcreteObserver implements Observer {
public void update(object sender) {
/* Do something with the sender */
}
}
class Object {
};
class Subject : public Object {
private:
list<Observer*>* _observers;
string _state;
public:
Subject() {
_observers = new list<Observer*>();
}
string GetState() {
return _state;
}
void SetState(string value) {
_state = value;
Notify();
}
void Attach(Observer* o) {
_observers->push_back(o);
}
void Detach(Observer* o) {
_observers->remove(o);
}
protected:
void Notify() {
for (list<Observer*>::iterator o = _observers->begin(); o != _observers->end(); ++o) {
(*o)->Update(this);
}
}
};
class Observer {
public:
virtual void Update(Object* sender) = 0;
};
class ConcreteObserver : public Observer {
public:
void Update(Object* sender) {
/* Do something with the sender */
}
};
Practical considerations
- Practically the observer pattern has some implementation dependent flaws (e.g. a framework or a custom implementation)
- Main problem: How to cleanly detach an event listener?
- In managed languages memory leaks can occur
- Here weak references provide a way out of this mess
- In native languages segmentation faults are possible
- Hence: Always think about how (and when) to remove the observer
A stock ticker
public struct Stock
{
public double Price { get; set; }
public string Code { get; set; }
}
public interface IStockObserverBase
{
void Notify(Stock stock);
}
public class SpecificStockObserver : IStockObserverBase
{
public SpecificStockObserver(string name)
{
Name = name;
}
public string Name
{
get;
private set;
}
public void Notify(Stock stock)
{
if(stock.Code == Name)
Console.WriteLine("{0} changed to {1:C}", stock.Code, stock.Price);
}
}
public abstract class StockTickerBase
{
readonly protected List<IStockObserverBase> _observers = new List<IStockObserverBase>();
public void Register(IStockObserverBase observer)
{
if(!_observers.Contains(observer))
{
_observers.Add(observer);
}
}
public void Unregister(IStockObserverBase observer)
{
if (_observers.Contains(observer))
{
_observers.Remove(observer);
}
}
}
public class StockTicker : StockTickerBase
{
private List<Stock> stocks = new List<Stock>();
public void Change(string code, double price)
{
Stock stock = new Stock
{
Code = code,
Price = price
};
if (stocks.Contains(stock))
stocks.Remove(stock);
stocks.Add(stock);
Notify(stock);
}
void Notify(Stock stock)
{
foreach (var observer in _observers)
{
observer.Notify(stock);
}
}
}
The command pattern
- Commands are important and exist in various forms
- They could be command line arguments or input, events, ...
- Today they are mostly present by clicking on buttons in GUI
- The command pattern defines four terms:
- command (the central object)
- receiver (contains specific method(s))
- invoker (uses the command for invocation)
- client (knows when to pass the command to the invoker)
An illustrative example
- As an example let's take a simple application with a GUI
- The client would be a
Button
- There are multiple invokers like a
Click
- Each invoker has a number of commands defined
- A command connects the defined invoker of an existing
Button
with our own implementation (receiver) - The command is therefore something like an abstraction of the underlying implementation
Command diagram
Remarks
- The command pattern is quite similar to the factory method, but instead of creation it is about execution
- The terminology is not consistent and often confusing
- Implementations might consider having do and undo instead of execute
- Also a Boolean indicator if the command can be executed might make sense (usually this is a get method)
- Commands are a great extension to the observer pattern
Light commands
public interface ICommand
{
void Execute();
}
public class Switch
{
private List<ICommand> _commands = new List<ICommand>();
public void StoreAndExecute(ICommand command)
{
_commands.Add(command);
command.Execute();
}
}
public class Light
{
public void TurnOn()
{
Console.WriteLine("The light is on");
}
public void TurnOff()
{
Console.WriteLine("The light is off");
}
}
public class FlipUpCommand : ICommand
{
private Light _light;
public FlipUpCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
}
public class FlipDownCommand : ICommand
{
private Light _light;
public FlipDownCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOff();
}
}
public interface Command {
void execute();
}
public class Switch {
private List<Command> history = new ArrayList<Command>();
public Switch() {
}
public void storeAndExecute(Command command) {
this.history.add(command);
command.execute();
}
}
public class Light {
public Light() {
}
public void turnOn() {
System.out.println("The light is on");
}
public void turnOff() {
System.out.println("The light is off");
}
}
public class FlipUpCommand implements Command {
private Light theLight;
public FlipUpCommand(Light light) {
this.theLight = light;
}
public void execute(){
theLight.turnOn();
}
}
public class FlipDownCommand implements Command {
private Light theLight;
public FlipDownCommand(Light light) {
this.theLight = light;
}
public void execute() {
theLight.turnOff();
}
}
class Command {
public:
virtual void Execute() = 0;
};
class Switch {
private:
list<Command*>* history;
public:
Switch() {
history = new list<Command*>();
}
public void StoreAndExecute(Command* command) {
history->push_back(command);
command->execute();
}
};
class Light {
public:
Light() {
}
void TurnOn() {
cout << "The light is on" << endl;
}
void TurnOff() {
cout << "The light is off" << endl;
}
};
class FlipUpCommand : public Command {
private:
Light* theLight;
public:
FlipUpCommand(Light* light) {
theLight = light;
}
void Execute() {
theLight->TurnOn();
}
};
class FlipDownCommand : public Command {
private:
Light* theLight;
public:
FlipDownCommand(Light* light) {
theLight = light;
}
void Execute() {
theLight->TurnOff();
}
};
Practical considerations
- There are some points when the command pattern is really useful:
- A history of requests is needed
- Callback functionality is required
- Requests are handled at variant times / orders
- Invoker and client should be decoupled
- Commands are useful for wizards, progress bars, GUI buttons, menu actions, and other transactional behavior
Controlling a robot
public abstract class RobotCommandBase
{
protected Robot _robot;
public RobotCommandBase(Robot robot)
{
_robot = robot;
}
public abstract void Execute();
public abstract void Undo();
}
public class MoveCommand:RobotCommandBase
{
public int ForwardDistance { get; set; }
public MoveCommand(Robot robot) : base(robot) { }
public override void Execute()
{
_robot.Move(ForwardDistance);
}
public override void Undo()
{
_robot.Move(-ForwardDistance);
}
}
public class RotateLeftCommand : RobotCommandBase
{
public double LeftRotationAngle { get; set; }
public RotateLeftCommand(Robot robot) : base(robot) { }
public override void Execute()
{
_robot.RotateLeft(LeftRotationAngle);
}
public override void Undo()
{
_robot.RotateRight(LeftRotationAngle);
}
}
public class RotateRightCommand : RobotCommandBase
{
public double RightRotationAngle { get; set; }
public RotateRightCommand(Robot robot) : base(robot) { }
public override void Execute()
{
_robot.RotateRight(RightRotationAngle);
}
public override void Undo()
{
_robot.RotateLeft(RightRotationAngle);
}
}
public class TakeSampleCommand : RobotCommandBase
{
public bool TakeSample { get; set; }
public TakeSampleCommand(Robot robot) : base(robot) { }
public override void Execute()
{
_robot.TakeSample(true);
}
public override void Undo()
{
_robot.TakeSample(false);
}
}
public class RobotController
{
private Queue<RobotCommandBase> commands;
private Stack<RobotCommandBase> undoStack;
public RobotController()
{
commands = new Queue<RobotCommandBase>();
undoStack = new Stack<RobotCommandBase>();
}
public void EnqueueCommand(RobotCommandBase command)
{
commands.Enqueue(command);
}
public void ClearCommands()
{
commands.Clear();
}
public void ExecuteAllCommands()
{
Console.WriteLine("EXECUTING COMMANDS.");
while (commands.Count > 0)
{
RobotCommandBase command = commands.Dequeue();
command.Execute();
undoStack.Push(command);
}
}
public void UndoCommands(int numUndos)
{
Console.WriteLine("REVERSING {0} COMMAND(S).", numUndos);
while (numUndos > 0 && undoStack.Count > 0)
{
RobotCommandBase command = undoStack.Pop();
command.Undo();
numUndos--;
}
}
}
public class Robot
{
public void Move(int distance)
{
if (distance > 0)
Console.WriteLine("Robot moved forwards {0}mm.", distance);
else
Console.WriteLine("Robot moved backwards {0}mm.", -distance);
}
public void RotateLeft(double angle)
{
if (angle > 0)
Console.WriteLine("Robot rotated left {0} degrees.", angle);
else
Console.WriteLine("Robot rotated right {0} degrees.", -angle);
}
public void RotateRight(double angle)
{
if (angle > 0)
Console.WriteLine("Robot rotated right {0} degrees.", angle);
else
Console.WriteLine("Robot rotated left {0} degrees.", -angle);
}
public void TakeSample(bool take)
{
if(take)
Console.WriteLine("Robot took sample");
else
Console.WriteLine("Robot released sample");
}
}
The chain-of-responsibility pattern
- Quite often we want to execute various commands in a certain way
- Creating a chain of responsibility ensures a loosely coupled system
- The chain uses a source of (atomic) commands a series of processing objects, which contain the logic
- Classically the pattern defines a linked list of handlers
- The direction is not fixed, it could also be a tree of responsibility
- In general this is a perfect extension to the command pattern
Chain-of-responsibility diagram
Remarks
- The command is passed to the first processing object which can handle this command or send to its successor
- A single handler only needs to know its successor, if any
- This is a big plus, but might lead to a circle (and infinite loop)
- Also the chain is only as good as its weakest member
- This means that if the last handler is not responsible for the request, it will not execute the build chain of commands
A simple sample
public abstract class HandlerBase
{
public HandlerBase Successor
{
get;
set;
}
public abstract void HandleRequest(int request);
}
public class ConcreteHandlerA : HandlerBase
{
public override void HandleRequest(int request)
{
if (request == 1)
Console.WriteLine("Handled by ConcreteHandlerA");
else if (Successor != null)
Successor.HandleRequest(request);
}
}
public class ConcreteHandlerB : HandlerBase
{
public override void HandleRequest(int request)
{
if (request > 10)
Console.WriteLine("Handled by ConcreteHandlerB");
else if (Successor != null)
Successor.HandleRequest(request);
}
}
public abstract class HandlerBase {
private Handlerbase successor;
public HandlerBase getSuccessor() {
return successor;
}
public void setSuccessor(HandlerBase value) {
successor = value;
}
public abstract void handleRequest(int request);
}
public class ConcreteHandlerA extends HandlerBase {
@Override
public void handleRequest(int request) {
if (request == 1)
out.println("Handled by ConcreteHandlerA");
else if (getSuccessor() != null)
getSuccessor().handleRequest(request);
}
}
public class ConcreteHandlerB extends HandlerBase {
@Override
public void handleRequest(int request) {
if (request > 10)
out.println("Handled by ConcreteHandlerB");
else if (getSuccessor() != null)
getSuccessor().handleRequest(request);
}
}
class HandlerBase {
private:
Handlerbase* successor;
public:
HandlerBase* GetSuccessor() {
return successor;
}
void SetSuccessor(HandlerBase* value) {
successor = value;
}
virtual void HandleRequest(int request) = 0;
};
class ConcreteHandlerA : public HandlerBase {
public:
void HandleRequest(int request) {
if (request == 1)
cout << "Handled by ConcreteHandlerA" << endl;
else if (GetSuccessor() != NULL)
getSuccessor()->HandleRequest(request);
}
};
class ConcreteHandlerB : public HandlerBase {
public:
void HandleRequest(int request) {
if (request > 10)
cout << "Handled by ConcreteHandlerB" << endl;
else if (GetSuccessor() != NULL)
getSuccessor()->HandleRequest(request);
}
};
Practical considerations
- Use this pattern if more than one handler for a request is available
- Otherwise if one handler might require another handler
- Or if the set of handlers varies dynamically
- Chain-of-responsibility patterns are great for filtering
- The biggest advantage is the extensibility
- Also the specific handler does not have to be known (information hiding)
Chain-of-responsibility Vs Command
Chain-of-responsibility | Command | |
---|---|---|
Client creates | Handlers | Commands |
Variations of | Handlers | Commands, receivers |
Clients can use | Multiple handlers | Different receivers |
Client calls | Handlers | Receivers |
Work done in | Handler | Receiver |
Decisions based on | Limits in handlers | Routing in commands |
Unknown requests? | Received, not handled | Executes nothing |
A coin machine
public class Coin
{
public float Weight { get; set; }
public float Diameter { get; set; }
}
public enum CoinEvaluationResult
{
Accepted,
Rejected
}
public abstract class CoinHandlerBase
{
protected CoinHandlerBase _successor;
public void SetSuccessor(CoinHandlerBase successor)
{
_successor = successor;
}
public abstract CoinEvaluationResult EvaluateCoin(Coin coin);
}
class FiftyPenceHandler : CoinHandlerBase
{
public override CoinEvaluationResult EvaluateCoin(Coin coin)
{
if (Math.Abs(coin.Weight - 8) < 0.02 && Math.Abs(coin.Diameter - 27.3) < 0.15)
{
Console.WriteLine("Captured 50p");
return CoinEvaluationResult.Accepted;
}
if (_successor != null)
{
return _successor.EvaluateCoin(coin);
}
return CoinEvaluationResult.Rejected;
}
}
class FivePenceHandler : CoinHandlerBase
{
public override CoinEvaluationResult EvaluateCoin(Coin coin)
{
if (Math.Abs(coin.Weight - 3.25) < 0.02 && Math.Abs(coin.Diameter - 18) < 0.1)
{
Console.WriteLine("Captured 5p");
return CoinEvaluationResult.Accepted;
}
if (_successor != null)
{
return _successor.EvaluateCoin(coin);
}
return CoinEvaluationResult.Rejected;
}
}
class OnePoundHandler : CoinHandlerBase
{
public override CoinEvaluationResult EvaluateCoin(Coin coin)
{
if (Math.Abs(coin.Weight - 9.5) < 0.02 && Math.Abs(coin.Diameter - 22.5) < 0.13)
{
Console.WriteLine("Captured £1");
return CoinEvaluationResult.Accepted;
}
if (_successor != null)
{
return _successor.EvaluateCoin(coin);
}
return CoinEvaluationResult.Rejected;
}
}
class TenPenceHandler : CoinHandlerBase
{
public override CoinEvaluationResult EvaluateCoin(Coin coin)
{
if (Math.Abs(coin.Weight - 6.5) < 0.03 && Math.Abs(coin.Diameter - 24.5) < 0.15)
{
Console.WriteLine("Captured 10p");
return CoinEvaluationResult.Accepted;
}
if (_successor != null)
{
return _successor.EvaluateCoin(coin);
}
return CoinEvaluationResult.Rejected;
}
}
class TwentyPenceHandler : CoinHandlerBase
{
public override CoinEvaluationResult EvaluateCoin(Coin coin)
{
if (Math.Abs(coin.Weight - 5) < 0.01 && Math.Abs(coin.Diameter - 21.4) < 0.1)
{
Console.WriteLine("Captured 20p");
return CoinEvaluationResult.Accepted;
}
if (_successor != null)
{
return _successor.EvaluateCoin(coin);
}
return CoinEvaluationResult.Rejected;
}
}
The iterator pattern
- Sometimes we want to iterate over a bunch of objects
- This is actually like handling a linked list, e.g. the previous pattern
- Traversing lists, trees, and other structures is important
- Key: Iterating without knowing the explicit structure
- Goal: Provide a simple interface for traversing a collection of items
- Languages like C# or Java have the iterator pattern build in
Iterator diagram
Remarks
- In C++ the pattern is usually implemented with pointer operators
- Java and C# already provide an interface to implement
- Usually there is a generic interface, which should be used
- The iterator pattern enables technologies such as LINQ
- Passing iterators as arguments can be very efficiently
- Reason: The iterator object maintains the state of the iteration
Trivial iterator
interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
interface IEnumerable
{
IEnumerator GetEnumerator();
}
class MyClass : IEnumerable
{
object[] array;
/* ... */
public IEnumerator GetEnumerator()
{
return new ArrayIterator(array);
}
}
class ArrayIterator : IEnumerator
{
int index;
object[] array;
public ArrayIterator (object[] array)
{
this.array = array;
Reset();
}
public object Current
{
get { return index >= 0 && index < array.Length ? array[index] : null; }
}
public bool MoveNext()
{
index++;
return Current != null;
}
public void Reset()
{
index = -1;
}
}
interface Enumerator {
object getCurrent();
bool moveNext();
void reset();
}
interface Enumerable {
Enumerator getEnumerator();
}
class MyClass implements Enumerable {
object[] array;
/* ... */
public Enumerator getEnumerator() {
return new ArrayIterator(array);
}
}
class ArrayIterator implements Enumerator {
int index;
object[] array;
public ArrayIterator (object[] array) {
this.array = array;
Reset();
}
public object getCurrent() {
return index >= 0 && index < array.Length ? array[index] : null;
}
public bool moveNext() {
index++;
return Current != null;
}
public void reset() {
index = -1;
}
}
Practical considerations
- C# and Java have a loop construct based on the iterator pattern
- Here
GetEnumerator()
is called implicitely - Then
MoveNext()
is used until false is returned - This makes
foreach()
andfor(:)
more expensive than the classic for loop, which does not require any function calls - Advantage: We can iterate over arbitrary objects
- In C++ we can always use pointer overloads to achieve this
A simple stack iterator
class Stack {
int items[10];
int sp;
public:
friend class StackIter;
Stack() {
sp = -1;
}
void Push(int in) {
items[++sp] = in;
}
int Pop() {
return items[sp--];
}
bool IsEmpty() {
return (sp == - 1);
}
StackIterator* CreateIterator() const {
return new StackIterator(this);
}
};
class StackIterator {
const Stack *stk;
int index;
public:
StackIter(const Stack *s) {
stk = s;
}
void Reset() {
index = -1;
}
bool MoveNext() {
return ++index == stk->sp + 1;
}
int GetCurrentItem() {
return stk->items[index];
}
};
Visitor pattern
- Separates a set of structured data from the functionality
- Promotes loose coupling
- Enables additional operations
- However, the data model (structure) is therefore really limited
- Basically it is just a container, which requires somebody to visit and evaluate its data
Visitor diagram
Remarks
- New operations are added by creating new visitors
- Similar operations are managed in one visitor and separated from others
- A visitor can work over multiple class hierarchies
- However, the nice extensibility with visitors comes with greater constraints for the specific elements
- Compilers usually traverse the object tree via the visitor pattern
- In general the visitor pattern helps to apply operations on structures without information on the structure
Visitor sample
abstract class Visitor
{
public abstract void VisitConcreteElementA(ConcreteElementA concreteElementA);
public abstract void VisitConcreteElementB(ConcreteElementB concreteElementB);
}
class ConcreteVisitor : Visitor
{
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
/* ... */
}
public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
/* ... */
}
}
abstract class Element
{
public abstract void Accept(Visitor visitor);
}
class ConcreteElementA : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementA(this);
}
}
class ConcreteElementB : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementB(this);
}
}
class ObjectStructure
{
private List<Element> _elements;
public ObjectStructure()
{
_elements = new List<Element>();
}
public void Attach(Element element)
{
_elements.Add(element);
}
public void Detach(Element element)
{
_elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element element in _elements)
{
element.Accept(visitor);
}
}
}
abstract class Visitor {
public abstract void visitConcreteElementA(ConcreteElementA concreteElementA);
public abstract void visitConcreteElementB(ConcreteElementB concreteElementB);
}
class ConcreteVisitor extends Visitor {
@Override
public void visitConcreteElementA(ConcreteElementA concreteElementA) {
/* ... */
}
@Override
public void visitConcreteElementB(ConcreteElementB concreteElementB) {
/* ... */
}
}
abstract class Element {
public abstract void accept(Visitor visitor);
}
class ConcreteElementA extends Element {
@Override
public void accept(Visitor visitor) {
visitor.visitConcreteElementA(this);
}
}
class ConcreteElementB extends Element {
@Override
public void accept(Visitor visitor) {
visitor.visitConcreteElementB(this);
}
}
class ObjectStructure {
private ArrayList<Element> _elements;
public ObjectStructure() {
_elements = new ArrayList<Element>();
}
public void attach(Element element) {
_elements.add(element);
}
public void detach(Element element) {
_elements.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : _elements) {
element.accept(visitor);
}
}
}
class Visitor {
public:
virtual void VisitConcreteElementA(ConcreteElementA* concreteElementA) = 0;
virtual void VisitConcreteElementB(ConcreteElementB* concreteElementB) = 0;
};
class ConcreteVisitor : public Visitor {
public:
void VisitConcreteElementA(ConcreteElementA* concreteElementA) {
/* ... */
}
void VisitConcreteElementA(ConcreteElementB* concreteElementA) {
/* ... */
}
};
class Element {
public:
virtual void Accept(Visitor* visitor) = 0;
};
class ConcreteElementA : public Element {
public:
void Accept(Visitor* visitor) {
visitor->VisitConcreteElementA(this);
}
};
class ConcreteElementB : public Element {
public:
void Accept(Visitor* visitor) {
visitor->VisitConcreteElementB(this);
}
};
class ObjectStructure {
private:
list<Element*>* _elements;
public:
ObjectStructure() {
_elements = new list<Element*>();
}
void Attach(Element* element) {
_elements->push_back(element);
}
void Detach(Element* element) {
_elements->remove(element);
}
void Accept(Visitor* visitor) {
for (list<Element*>::iterator it = _elements->begin(); it != _elements->end(); ++it) {
(*it)->Accept(visitor);
}
}
};
Practical considerations
- The iterator pattern and visitor pattern has the same benefit, they are used to traverse object structures
- The visitor pattern can be used on complex structure such as hierarchical structures or composite structures
- Drawback: If a new visitable object is added to the framework structure all the implemented visitors need to be modified
- Part of the dependency problems can be solved by using reflection with a performance cost
Operations on employees
interface IVisitor
{
void Visit(Element element);
}
class IncomeVisitor : IVisitor
{
public void Visit(Element element)
{
Employee e = element as Employee;
if (e != null)
{
e.Income *= 1.10;
Console.WriteLine("{0} {1}'s new income: {2:C}", e.GetType().Name, e.Name, e.Income);
}
}
}
class VacationVisitor : IVisitor
{
public void Visit(Element element)
{
Employee e = element as Employee;
if (e != null)
{
Console.WriteLine("{0} {1}'s new vacation days: {2}", e.GetType().Name, e.Name, e.VacationDays);
}
}
}
abstract class Element
{
public abstract void Accept(IVisitor visitor);
}
class Employee : Element
{
private string _name;
private double _income;
private int _vacationDays;
public Employee(string name, double income, int vacationDays)
{
this._name = name;
this._income = income;
this._vacationDays = vacationDays;
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public double Income
{
get { return _income; }
set { _income = value; }
}
public int VacationDays
{
get { return _vacationDays; }
set { _vacationDays = value; }
}
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
class Employees
{
private List<Employee> _employees;
public Employees()
{
_employees = new List<Employee>();
}
public void Attach(Employee employee)
{
_employees.Add(employee);
}
public void Detach(Employee employee)
{
_employees.Remove(employee);
}
public void Accept(IVisitor visitor)
{
foreach (Employee e in _employees)
{
e.Accept(visitor);
}
}
}
class Clerk : Employee
{
public Clerk() : base("Hank", 25000.0, 14)
{
}
}
class Director : Employee
{
public Director() : base("Elly", 35000.0, 16)
{
}
}
class President : Employee
{
public President() : base("Dick", 45000.0, 21)
{
}
}
State pattern
- Classes have responsibilities and contain logic, but sometimes the logic can be messy
- A special case is a state-machine, where most logic is dependent on the current state
- The state pattern tries to simplify such a logic
- Idea: Encapsulate the state-dependent logic units as classes
- This allows to change behavior at run-time without changing the interface used to access the object
State diagram
Remarks
- The context holds the concrete state object that provides the behavior according to its current state
- This pattern closely resembles the strategy pattern (upcoming)
- A default behavior has to be defined (i.e. which state to use initially)
- Sometimes one state wants to change itself - this is not possible directly
- Here we could either make the state variable in the Context internal or public (or friend in C++) or return an element of type State
State sample
public class Context
{
private StateBase _state;
public Context(StateBase state)
{
State = state;
}
public void Request()
{
_state.Handle(this);
}
public StateBase State
{
get { return _state; }
set { _state = value; }
}
}
public abstract class StateBase
{
public abstract void Handle(Context context);
}
class ConcreteStateA : StateBase
{
public override void Handle(Context context)
{
context.State = new ConcreteStateB();
}
}
class ConcreteStateB : StateBase
{
public override void Handle(Context context)
{
context.State = new ConcreteStateA();
}
}
public class Context {
private StateBase _state;
public Context(StateBase state) {
setState(state);
}
public void request() {
_state.handle(this);
}
public StateBase getState() {
return _state;
}
public void setState(StateBase state) {
_state = state;
}
}
public abstract class StateBase {
public abstract void handle(Context context);
}
class ConcreteStateA extends StateBase {
@Override
public void handle(Context context) {
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB extends StateBase {
@Override
public void handle(Context context) {
context.setState(new ConcreteStateA());
}
}
class Context {
private:
StateBase* _state;
public:
Context(StateBase* state) {
SetState(state);
}
void Request() {
_state->Handle(this);
}
StateBase* GetState() {
return _state;
}
void SetState(StateBase* state) {
_state = state;
}
};
class StateBase {
public:
virtual void Handle(Context* context) = 0;
};
class ConcreteStateA : public StateBase {
public:
void Handle(Context* context) {
context->SetState(new ConcreteStateB());
}
};
class ConcreteStateB : public StateBase {
public:
void Handle(Context* context) {
context->SetState(new ConcreteStateA());
}
};
Practical considerations
- Instantiating states can be unnecessary
- Hence buffering state instances might be interesting
- In such cases the factory pattern might be helpful
- States also provide a simple mechanism for extending existing behavior
- We will see that similar patterns, like the template pattern or the strategy patterns exist
A DVD player
public abstract class DVDPlayerState
{
public abstract DVDPlayerState PlayButtonPressed(DVDPlayer player);
public abstract DVDPlayerState MenuButtonPressed(DVDPlayer player);
}
public class DVDPlayer
{
private DVDPlayerState _state;
public DVDPlayer() : this(new StandbyState())
{
}
public DVDPlayer(DVDPlayerState state)
{
_state = state;
}
public void PressPlayButton()
{
_state = _state.PlayButtonPressed(this);
}
public void PressMenuButton()
{
_state = _state.MenuButtonPressed(this);
}
}
class MenuState : DVDPlayerState
{
public MenuState()
{
Console.WriteLine("MENU");
}
public override void PlayButtonPressed(DVDPlayer player)
{
Console.WriteLine(" Next Menu Item Selected");
return this;
}
public override void MenuButtonPressed(DVDPlayer player)
{
return new StandbyState();
}
}
class MoviePausedState : DVDPlayerState
{
public MoviePausedState()
{
Console.WriteLine("MOVIE PAUSED");
}
public override DVDPlayerState PlayButtonPressed(DVDPlayer player)
{
return new MoviePlayingState();
}
public override DVDPlayerState MenuButtonPressed(DVDPlayer player)
{
return new MenuState();
}
}
class MoviePlayingState : DVDPlayerState
{
public MoviePlayingState()
{
Console.WriteLine("MOVIE PLAYING");
}
public override DVDPlayerState PlayButtonPressed(DVDPlayer player)
{
return new MoviePausedState();
}
public override DVDPlayerState MenuButtonPressed(DVDPlayer player)
{
return new MenuState();
}
}
class StandbyState : DVDPlayerState
{
public StandbyState()
{
Console.WriteLine("STANDBY");
}
public override DVDPlayerState PlayButtonPressed(DVDPlayer player)
{
return new MoviePlayingState();
}
public override DVDPlayerState MenuButtonPressed(DVDPlayer player)
{
return new MenuState();
}
}
... and the strategy pattern
Comparison
- The state pattern is usually quite tightly coupled to a context
- The strategy pattern is completely independent of a context and provides one solution to a problem
- Strategy lets the algorithm vary independently from clients that use it
- A common problem: Choosing one (of many) hashing algorithms
- Solution: An interface that defines the method and various implementations (e.g. HAVAL, MD5, SHA1, SHA2, ...)
... and the template pattern
- Quite similar to the state / strategy pattern is the template pattern
- This pattern is based on an abstract class, which defines a set of methods that need to be implemented
- Those methods are then called in a fixed sequence by some client
- While the Strategy design pattern overrides the entire algorithm, this pattern allows us to change only some parts of the behavior algorithm using abstract operations
Example template sequence
Memento pattern
- Saving or restoring states is quite important (e.g. undo, redo)
- The memento pattern defines a memento, a caretaker and an originator
- The originator wants to save or restore his state
- The whole state information is stored in an instance of a class, called memento
- The class responsible for managing the various states is the caretaker
Memento diagram
Remarks
- The memento is opaque to the caretaker, and the caretaker must not operate on it
- The memento must not allow any operation or access to the internal state
- However, the originator has to access the internal information for restoration
- The memento contains everything that is required for restoring a state
Memento sample
class Originator
{
private String state;
public void Set(String state)
{
this.state = state;
}
public Memento SaveToMemento()
{
return new Memento(state);
}
public void RestoreFromMemento(Memento memento)
{
state = memento.GetSavedState();
}
}
public class Memento
{
private readonly String state;
public Memento(String stateToSave)
{
state = stateToSave;
}
public String GetSavedState()
{
return state;
}
}
class Caretaker
{
List<Memento> savedStates;
Originator originator;
public Caretaker()
{
savedStates = new List<Memento>();
originator = new Originator();
}
public void ExampleAction()
{
originator.Set("State1");
originator.Set("State2");
savedStates.Add(originator.SaveToMemento());
originator.Set("State3");
savedStates.Add(originator.SaveToMemento());
originator.Set("State4");
originator.RestoreFromMemento(savedStates[1]);
}
}
class Originator {
private String state;
public void set(String state) {
this.state = state;
}
public Memento saveToMemento() {
return new Memento(state);
}
public void restoreFromMemento(Memento memento) {
state = memento.getSavedState();
}
}
public class Memento {
private final String state;
public Memento(String stateToSave) {
state = stateToSave;
}
public String getSavedState() {
return state;
}
}
class Caretaker {
List<Memento> savedStates;
Originator originator;
public Caretaker() {
savedStates = new ArrayList<Memento>();
originator = new Originator();
}
public void exampleAction() {
originator.set("State1");
originator.set("State2");
savedStates.add(originator.saveToMemento());
originator.set("State3");
savedStates.add(originator.saveToMemento());
originator.set("State4");
originator.restoreFromMemento(savedStates.get(1));
}
}
class Originator {
private:
String state;
public:
void Set(String state) {
this->state = state;
}
Memento* SaveToMemento() {
return new Memento(state);
}
void RestoreFromMemento(Memento* memento) {
state = memento->GetSavedState();
}
};
class Memento {
private:
const String state;
public:
Memento(String stateToSave) : state(stateToSave) {
}
public String GetSavedState() {
return state;
}
};
class Caretaker {
vector<Memento*>* savedStates;
Originator* originator;
public:
Caretaker() {
savedStates = new vector<Memento*>();
originator = new Originator();
}
void ExampleAction() {
originator->Set("State1");
originator->Set("State2");
savedStates->push_back(originator.SaveToMemento());
originator->Set("State3");
savedStates->push_back(originator.SaveToMemento());
originator->Set("State4");
originator->RestoreFromMemento(savedStates->at(1));
}
};
Specification pattern
- Most applications will be defined by set of rules
- Those rules are usually called business rules or business logic
- The specification pattern allows chaining or recombining objects, which are included in our business logic
- Usually this is build upon three logical operations (and, or, not)
- The classes itself (logic units) will decide if they are applicable
- Chaining is very important for this pattern
Specification diagram
Remarks
- The pattern can be used to remove a lot of cruft from a class's interface while decreasing coupling and increasing extensibility
- The primary use is to select a subset of objects based on some criteria
- Languages that allow delegates / function pointers in combination with generics may be already one step ahead
- The pattern's root is in Domain Driven Design for criteria tests
- Sometimes data structures are more complex and the visitor pattern might be a useful addition
Implementation in C#
public interface ISpecification<TEntity>
{
bool IsSatisfiedBy(TEntity entity);
}
internal class AndSpecification<TEntity> : ISpecification<TEntity>
{
private readonly ISpecification<TEntity> _spec1;
private readonly ISpecification<TEntity> _spec2;
protected ISpecification<TEntity> Spec1
{
get { return _spec1; }
}
protected ISpecification<TEntity> Spec2
{
get { return _spec2; }
}
internal AndSpecification(ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
if (spec1 == null)
throw new ArgumentNullException("spec1");
else if (spec2 == null)
throw new ArgumentNullException("spec2");
_spec1 = spec1;
_spec2 = spec2;
}
public bool IsSatisfiedBy(TEntity candidate)
{
return Spec1.IsSatisfiedBy(candidate) && Spec2.IsSatisfiedBy(candidate);
}
}
internal class OrSpecification<TEntity> : ISpecification<TEntity>
{
private readonly ISpecification<TEntity> _spec1;
private readonly ISpecification<TEntity> _spec2;
protected ISpecification<TEntity> Spec1
{
get { return _spec1; }
}
protected ISpecification<TEntity> Spec2
{
get { return _spec2; }
}
internal OrSpecification(ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
if (spec1 == null)
throw new ArgumentNullException("spec1");
else if (spec2 == null)
throw new ArgumentNullException("spec2");
_spec1 = spec1;
_spec2 = spec2;
}
public bool IsSatisfiedBy(TEntity candidate)
{
return Spec1.IsSatisfiedBy(candidate) || Spec2.IsSatisfiedBy(candidate);
}
}
internal class NotSpecification<TEntity> : ISpecification<TEntity>
{
private readonly ISpecification<TEntity> _wrapped;
protected ISpecification<TEntity> Wrapped
{
get { return _wrapped; }
}
internal NotSpecification(ISpecification<TEntity> spec)
{
if (spec == null)
throw new ArgumentNullException("spec");
_wrapped = spec;
}
public bool IsSatisfiedBy(TEntity candidate)
{
return !Wrapped.IsSatisfiedBy(candidate);
}
}
public static class ExtensionMethods
{
public static ISpecification<TEntity> And<TEntity>(this ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
return new AndSpecification<TEntity>(spec1, spec2);
}
public static ISpecification<TEntity> Or<TEntity>(this ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
return new OrSpecification<TEntity>(spec1, spec2);
}
public static ISpecification<TEntity> Not<TEntity>(this ISpecification<TEntity> spec)
{
return new NotSpecification<TEntity>(spec);
}
}
Mediator pattern
- To avoid strong coupling between two classes we can introduce a mediator class
- The mediator knows the two classes and establishes the connection
- Basically it is a reduction of complexity
- The reduction makes sense if both classes are quite heavy
- In most cases using a simple interface instead can be even better
Mediator diagram
Remarks
- Usually the mediator tries to be transparent
- For testability an interface is the best solution (mocking)
- Using a middle object decouples two classes
- We will see that this idea is the basis for more advanced techniques
- Basis: Reflection and compile-time decoupling by run-time wiring
- Dependency Injection uses a class that talks to all other classes
- The concrete classes do not have to be known
Two-way mediator sequence
Interpreter pattern
- Even simple applications might require some kind of DSL
- A DSL (Domain-Specific Language) is a kind of language for a special kind of application
- This is in contrast to GPL (general purpose languages)
- There are various types of DSLs like markup, modeling or programming languages
- Arguments of a command line might be already dynamic enough to define a DSL specification
How does the interpreter pattern help?
- The interpreter pattern allows the grammar for such a DSL to be represented as classes
- This (object-oriented) representation can then easily be extended
- The whole process takes information from a Context
- A Client creates the context and starts the interpreter by calling the interpret action
- This action is located on an object of type Expression
Interpreter diagram
Remarks
- The context contains information that is global to the interpreter
- The expressions represent the units of grammar
- The state pattern can be quite useful for parsing
- NonterminalExpression expressions are aggregates containing one or more (further) expressions (conditionally chained)
- The TerminalExpression implements an
interpret
operation associated with terminal symbols in the grammar - An instance is required for every terminal symbol in the given string
An interpreter for roman numbers
class Client
{
public static int Parse(string number)
{
Context context = new Context(number);
List<Expression> tree = new List<Expression>();
tree.Add(new ThousandExpression());
tree.Add(new HundredExpression());
tree.Add(new TenExpression());
tree.Add(new OneExpression());
foreach (Expression exp in tree)
exp.Interpret(context);
return context.Value;
}
}
class Context
{
private string query;
private int value;
public Context(string input)
{
query = input;
}
public string Query
{
get { return query; }
set { this.query = value; }
}
public int Value
{
get { return value; }
set { this.value = value; }
}
}
abstract class Expression
{
public void Interpret(Context context)
{
if (context.Query.Length == 0)
return;
if (context.Query.StartsWith(Nine))
{
context.Value += 9 * Weight;
context.Query = context.Query.Substring(Nine.Length);
}
else if (context.Query.StartsWith(Four))
{
context.Value += 4 * Weight;
context.Query = context.Query.Substring(Four.Length);
}
else if (context.Query.StartsWith(Five))
{
context.Value += 5 * Weight;
context.Query = context.Query.Substring(Five.Length);
}
while (context.Query.StartsWith(One))
{
context.Value += Weight;
context.Query = context.Query.Substring(One.Length);
}
}
public virtual string One { get { return " "; } }
public virtual string Four { get { return " "; } }
public virtual string Five { get { return " "; } }
public virtual string Nine { get { return " "; } }
public abstract int Weight { get; }
}
class ThousandExpression : Expression
{
public override string One { get { return "M"; } }
public override int Weight { get { return 1000; } }
}
class HundredExpression : Expression
{
public override string One { get { return "C"; } }
public override string Four { get { return "CD"; } }
public override string Five { get { return "D"; } }
public override string Nine { get { return "CM"; } }
public override int Weight { get { return 100; } }
}
class TenExpression : Expression
{
public override string One { get { return "X"; } }
public override string Four { get { return "XL"; } }
public override string Five { get { return "L"; } }
public override string Nine { get { return "XC"; } }
public override int Weight { get { return 10; } }
}
class OneExpression : Expression
{
public override string One { get { return "I"; } }
public override string Four { get { return "IV"; } }
public override string Five { get { return "V"; } }
public override string Nine { get { return "IX"; } }
public override int Weight { get { return 1; } }
}
class Client {
public static int parse(string number) {
Context context = new Context(number);
List<Expression> tree = new ArrayList<Expression>();
tree.add(new ThousandExpression());
tree.add(new HundredExpression());
tree.add(new TenExpression());
tree.add(new OneExpression());
for (Expression exp : tree)
exp.interpret(context);
return context.getValue();
}
}
class Context {
private string query;
private int value;
public Context(string input) {
query = input;
}
public string getQuery() {
return query;
}
public void setQuery(string value) {
this.query = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public void addToValue(int delta) {
this.value += delta;
}
}
abstract class Expression {
public void interpret(Context context) {
if (context.getQuery().length() == 0)
return;
if (context.getQuery().startsWith(getNine())) {
context.addToValue(9 * getWeight());
context.setQuery(context.getQuery().substring(getNine().length()));
} else if (context.getQuery().startsWith(getFour())) {
context.addToValue(4 * getWeight());
context.setQuery(context.getQuery().substring(getFour().length()));
} else if (context.getQuery().startsWith(getFive())) {
context.addToValue(5 * getWeight());
context.setQuery(context.getQuery().substring(getFive().length()));
}
while (context.getQuery().startsWith(getOne())) {
context.addToValue(getWeight());
context.setQuery(context.getQuery().substring(getOne().length()));
}
}
public virtual string getOne() {
return " ";
}
public virtual string getFour() {
return " ";
}
public virtual string getFive() {
return " ";
}
public virtual string getNine() {
return " ";
}
public abstract int getWeight();
}
class ThousandExpression extends Expression {
@Override
public string getOne() {
return "M";
}
@Override
public int getWeight() {
return 1000;
}
}
class HundredExpression extends Expression {
@Override
public string getOne() {
return "C";
}
@Override
public string getFour() {
return "CD";
}
@Override
public string getFive() {
return "D";
}
@Override
public string getNine() {
return "CM";
}
@Override
public int getWeight() {
return 100;
}
}
class TenExpression extends Expression {
@Override
public string getOne() {
return "X";
}
@Override
public string getFour() {
return "XL";
}
@Override
public string getFive() {
return "L";
}
@Override
public string getNine() {
return "XC";
}
@Override
public int getWeight() {
return 10;
}
}
class OneExpression extends Expression {
@Override
public string getOne() {
return "I";
}
@Override
public string getFour() {
return "IV";
}
@Override
public string getFive() {
return "V";
}
@Override
public string getNine() {
return "IX";
}
@Override
public int getWeight() {
return 1;
}
}
class Client {
public:
static int Parse(string number) {
Context context = new Context(number);
list<Expression*> tree;
tree.push_back(new ThousandExpression());
tree.push_back(new HundredExpression());
tree.push_back(new TenExpression());
tree.push_back(new OneExpression());
for (list<Expression*>::iterator it = tree.begin(); it != tree.end(); ++it)
it->Interpret(context);
return context->GetValue();
}
};
class Context {
private:
string query;
int value;
public:
Context(string input) {
query = input;
value = 0;
}
string GetQuery() {
return query;
}
void SetQuery(string value) {
this->query = value;
}
int GetValue() {
return value;
}
void SetValue(int value) {
this->value = value;
}
void AddToValue(int delta) {
this->value += delta;
}
};
class Expression {
public:
void Interpret(Context* context) {
if (context->GetQuery().length() == 0)
return;
if (context->GetQuery().find(GetNine()) == 0) {
context->AddToValue(9 * GetWeight());
context->SetQuery(context->GetQuery().substr(GetNine().length()));
} else if (context->GetQuery().find(GetFour()) == 0) {
context->AddToValue(4 * GetWeight());
context->SetQuery(context->GetQuery().substr(GetFour().length()));
} else if (context->GetQuery().find(GetFive()) == 0) {
context->AddToValue(5 * GetWeight());
context->SetQuery(context->GetQuery().substr(GetFive().length()));
}
while (context->GetQuery().find(GetOne()) == 0) {
context->AddToValue(GetWeight());
context->SetQuery(context->GetQuery().substr(GetOne().length()));
}
}
virtual string GetOne() {
return " ";
}
virtual string GetFour() {
return " ";
}
virtual string GetFive() {
return " ";
}
virtual string GetNine() {
return " ";
}
virtual int GetWeight() = 0;
};
class ThousandExpression : public Expression {
public:
string GetOne() {
return "M";
}
int GetWeight() {
return 1000;
}
};
class HundredExpression : public Expression {
public:
string GetOne() {
return "C";
}
string GetFour() {
return "CD";
}
string GetFive() {
return "D";
}
string GetNine() {
return "CM";
}
int GetWeight() {
return 100;
}
};
class TenExpression : public Expression {
public:
string GetOne() {
return "X";
}
string GetFour() {
return "XL";
}
string GetFive() {
return "L";
}
string GetNine() {
return "XC";
}
int GetWeight() {
return 10;
}
};
class OneExpression : public Expression {
public:
string GetOne() {
return "I";
}
string GetFour() {
return "IV";
}
string GetFive() {
return "V";
}
string GetNine() {
return "IX";
}
int GetWeight() {
return 1;
}
};
Practical considerations
- Basic idea: A class for each defined symbol
- The structure of the syntax tree is given by the composite pattern
- Compilers are usually not that simple, since they are more fine-grained
- Simple languages like SQL are, however, perfect suitable
- In practice the client is called the engine and invoked from a real client
- The client usually already contains some logic
Literature
- Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1995). Design Patterns: Elements of Reusable Object Oriented Software.
- Freeman, Eric; Freeman, Elisabeth; Kathy, Sierra; Bert, Bates (2004). Head First Design Patterns.
- Buschmann, Frank; Meunier, Regine; Rohnert, Hans; Sommerlad, Peter (1996). Pattern-Oriented Software Architecture, Volume 1: A System of Patterns.
- Fowler, Martin (2002). Patterns of Enterprise Application Architecture.