[go: up one dir, main page]

0% found this document useful (0 votes)
11 views80 pages

Chapter 32 (Behavioural Design Patterns)

yes

Uploaded by

chingayanivelle
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views80 pages

Chapter 32 (Behavioural Design Patterns)

yes

Uploaded by

chingayanivelle
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 80

1

Chapter 32
Behavioural Design Patterns
1. Introduction

Behavioural patterns are concerned with algorithms and the assignment of responsibilities
between objects. They are mostly concerned with communication between objects.

2. Chain of Responsibility (CoR)

2.1 Introduction

The Chain of Responsibility (CoR) pattern allows a number of classes to handle a request
without any of them knowing about the capabilities of the other classes. Requests are passed
along a chain of handlers. Upon receiving a request, each handler processes the request if
possible and then passes it on to the next handler in the chain if necessary and possible. It
provides a loose coupling between these classes; the only common link is the request that is
passed between them.

The pattern can be used when an expenditure must be approved on the lowest level for which
the level is authorised. It can also be used to control access on different levels or when
diagnosing a patient based on symptoms (see the FPM link below). It can also be used to execute
a series of steps in a predefined order.

• FPM:
https://www.aafp.org/journals/fpm/blogs/inpractice/entry/covid_diagnosis_flowcharts.html

2.2 Conceptual class diagram

Figure 1. CoR pattern - Conceptual

Copyright: PJ Blignaut, 2022


2

1. The Handler declares the interface which must be implemented by all concrete handlers. It
has a method for handling requests as well as a method for setting the next handler on the
chain.
2. The Base Handler is an optional abstract class that contains the code that are common to all
handler classes. Usually, this class defines a field for storing a reference to the next handler.
3. Concrete Handlers contain the actual code for processing requests. Upon receiving a request,
each handler must decide whether to process it and, additionally, whether to pass it along the
chain.
4. The Client declares all handlers and set up the chain of responsibility.

2.3 Example of authorisation on different levels

Consider the scenario of a business where expenditures must be approved by different


levels of management, depending on the amount. Amounts less than R10,000 can be
approved by a director. Amounts R10,000 or more but less than R25,000 must be approved
by the vice-president. The president can approve amounts less than R100,000. Amounts of
R100,000 or more must be approved by the board.

Handler interface

interface IApprover
{
void SetSuccessor(IApprover successor);
string ProcessRequest(decimal amount);
} //interface IApprover

Base handler

Note that the class is abstract and the ProcessRequest method is abstract. It must be
overridden in the concrete handlers.

abstract class AApprover : IApprover //Is a


{
//Instance of handler interface
protected IApprover successor; //Has a

//Implementation of the SetNext method in the handler interface


public void SetSuccessor(IApprover successor)
{
this.successor = successor;
}

//Implementation of the Handle method in the handler interface


public abstract string ProcessRequest(decimal amount);
} //abstract class AApprover

Concrete handler

A separate class is defined for every level of approval. Each class overrides the
ProcessRequest method with the specific business rules as applicable.

It is important to
(i) decide if the handler can do the work itself or have to pass it on to the next level;
(ii) check if the next level handler is defined;
(iii) handle the request in case there is no next handler.

Copyright: PJ Blignaut, 2022


3

class Director : AApprover


{
public override string ProcessRequest(decimal amount)
{
if (amount < 10000.0m)
return "Director approved request";
else if (successor != null)
return successor.ProcessRequest(amount);
return "Request was not approved";
}
} //class Director

class VicePresident : AApprover


{
public override string ProcessRequest(decimal amount)
{
if (amount < 25000.0m)
return "Vice-president approved request";
else if (successor != null)
return successor.ProcessRequest(amount);
return "Request was not approved";
}
} //VicePresident

class President : AApprover


{
public override string ProcessRequest(decimal amount)
{
if (amount < 100000.0m)
return "President approved request";
return "Request requires a board meeting!";
}
} //President

Client

The Client class instantiates all handlers and sets up the chain of responsibility. It then kicks
off the processing chain by calling the handler at the lowest level in the hierarchy.

class Client
{
static void Main(string[] args)
{
//Enter amount
Console.Write("\tEnter amount: ");
decimal amount = decimal.Parse(Console.ReadLine());

//Instantiate handlers
Approver director = new Director();
Approver vp = new VicePresident();
Approver pres = new President();

//Setup Chain of Responsibility


director.SetSuccessor(vp);
vp.SetSuccessor(pres);

//Process expenditure
Console.WriteLine("\t" + director.ProcessRequest(amount));

//Wait for user


Console.Write("\n\tPress any key to exit ..."); Console.ReadKey();
} //Main
} //class Client

Copyright: PJ Blignaut, 2022


4

Class diagram

Figure 2. Class diagram of the CoR pattern

2.4 Example of sequence of steps

CoR can also be used to specify the order of execution of steps. The following, rather
conceptual, example shows how that is done.

Handler interface

interface IHandler
{
void SetSuccessor(IHandler successor);
string Handle();
}

Base handler

abstract class AHandler : IHandler


{
protected IHandler successor;

public abstract string Handle();

public void SetSuccessor(IHandler successor)


{
this.successor = successor;
}
}

Copyright: PJ Blignaut, 2022


5

Concrete handlers

class Step1 : AHandler


{
public override string Handle()
{
//Do whatever must be done on this step and pass on to the next step
string s = "\tStep 1\n";
return successor != null ? s + successor.Handle() : s;
}
}

class Step2 : Ahandler {... }


class Step3 : Ahandler {... }
class Step4 : Ahandler {... }

Client

class Client
{
static void Main(string[] args)
{
//Instantiate step handlers
IHandler step1 = new Step1();
IHandler step2 = new Step2();
IHandler step3 = new Step3();
IHandler step4 = new Step4();

//Set up chain of responsibility


step1.SetSuccessor(step3);
step2.SetSuccessor(step4);
step3.SetSuccessor(step2);

//Handle steps – start with the first one


Console.WriteLine(step1.Handle());

Console.Write("\n\tPress any key to exit ... ");


Console.ReadKey();
} //Main
} //class Client

2.5 Other examples

• https://refactoring.guru/design-patterns/chain-of-responsibility/csharp/example#lang-
features
• https://refactoring.guru/design-patterns/chain-of-responsibility
• https://www.dofactory.com/net/chain-of-responsibility-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/chain-of-responsibility-design-pattern-
dotnet
• Test 1, 2020, Question 6 (Approval of expenditure)
• Project 7, 2020 (Admission to CSI)
• Test 2, 2021, Question 3 (Theory)
• Project 6, 2022 (Wireless network troubleshooter)

Copyright: PJ Blignaut, 2022


6

2.6 When to 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 is essential to execute several handlers in a particular order.

2.7 Pros and cons

Advantages

• You can control the order of request handling.


• Single Responsibility Principle. You can decouple classes that invoke operations from
classes that perform operations.
• Open/Closed Principle. You can introduce new handlers into the app without breaking the
existing client code.

Disadvantage

• Some requests may end up unhandled.

2.8 Further reading

• Cardoso, p 263
• Cooper, Chapter 21, 358
• Gamma, Helm, Johnson, Vlissides (1995, p 284)
• Nesteruk (2019, p 185)

3. Command pattern

3.1 Introduction

With the Command pattern, a request is turned into a stand-alone object that contains all
information about the request. This transformation lets you parameterize methods with different
requests.

Commands are operations that must be executed on a class, and they can change the state of an
object in the class. We are used to have operations as methods inside a class, but with the
command pattern, the operations are in another class.

With this pattern, we must identify three entities, namely the invoker, receiver, and command.
Depending om the scenario, the client may act as invoker, although that may (i) limit the
functionality of the invoker and (ii) put too much responsibility on the client.

• In a bank account system, the receiver will be the account class. Operations might be deposit
and withdraw. The invoker will be the ATM or interface used by the account holder.
• In a text editor, the receiver will be a document, the invoker will be a word processing
program and the commands may be Insert, Delete, Cut, Copy, Paste, etc.
• For a TV with remote control, the receiver is the TV, the invoker is the remote control and
commands will be channel up, channel down, volume up, volume down, etc.

Copyright: PJ Blignaut, 2022


7

• For a light in the room, the receiver is the bulb, the invoker is the switch, and the commands
will be SwitchOn and SwitchOff.

3.2 Conceptual class diagram

Figure 3. Command pattern - conceptual

1. The Invoker (aka Sender) class is responsible for initiating requests. This class may have a
private field for storing a reference to a command object. The sender triggers that command
instead of sending the request directly to the receiver. Note that the sender is not responsible
for creating the command object. Usually, it gets a pre-created command from the client via
the constructor or else it may be specified as a parameter of the ExecuteCommand method.

2. The Command interface usually declares just a single method for executing the command.

3. Concrete commands implement various kinds of requests. Parameters required to execute a


method on a receiving object can be declared as fields in the concrete command. You can
make command objects immutable by only allowing the initialization of these fields via the
constructor.

4. The Receiver class contains some business logic. Almost any object may act as a receiver.
Most commands only handle the details of how a request is passed to the receiver, while the
receiver itself does the actual work.

5. The Client creates and configures concrete command objects. The client must pass all of the
request parameters, including a receiver instance, into the command’s constructor.

3.3 Example

Consider the account of an ATM system. This example is very limited but serves to illustrate
the principles.

Copyright: PJ Blignaut, 2022


8

Receiver

The actual operation and checking of business rules, e.g. Balance > 0, is done by the receiver.

public class Account


{
public decimal Balance { get; private set; }
public void SetBalance(decimal amount)
{
if (amount >= 0 )
Balance = amount;
}
} //Account

Invoker

The invoker is responsible to trigger execution of the given command. Actually, if we don't use
the invoker to keep track of commands in a stack (see later), we get away without it. The client
will then call the Execute command directly.

public class ATM


{
public void ExecuteCommand(ICommand command)
{
command.Execute();
}
}

Command interface

public interface ICommand { void Execute(); }

Note: If you want individual commands to return a value, you can set the return type of Execute
to object and then cast the result to the applicable type in the client.

Commands

Every operation, Deposit, Withdraw, etc. goes into a separate class. Every command class
inherits from ICommand and must implement the Execute method. The receiver of the
command and any scenario-specific values are specified through the constructor parameters.

public class Deposit : ICommand


{
private Account account;
private decimal amount;

//Constructor
public Deposit(Account account, decimal amount)
{
this.account = account;
this.amount = amount;
}

public void Execute()


{
account.SetBalance(account.Balance + amount);
}
} //class Deposit

Copyright: PJ Blignaut, 2022


9

public class Withdraw : ICommand


{
private Account account;
private decimal amount;

public Withdraw(Account account, decimal amount)


{
this.account = account;
this.amount = amount;
}

public void Execute()


{
account.SetBalance(account.Balance - amount);
}
} //class Withdraw

Client

The client references the receiver and invoker and instantiate separate command objects for
every operation.

class Client
{
static void Main(string[] args)
{
//Receiver: Account
Account account = new Account();

//Invoker: ATM
ATM atm = new ATM();

//Operation: Deposit
//- Create command
ICommand deposit = new Deposit(account, 100);
//- Execute
atm.ExecuteCommand(deposit);
Console.WriteLine("\tBalance: R " + account.Balance.ToString("0.00"));

//Operation: Withdraw
//- Create command and Execute in one go
atm.ExecuteCommand(new Withdraw(account, 70));
Console.WriteLine("\tBalance: R " + account.Balance.ToString("0.00"));

//Wait for user


Console.ReadKey();
} //Main
} //class Client

Copyright: PJ Blignaut, 2022


10

Class diagram

Figure 4. Command pattern applied to simple ATM system

3.4 Flow of logic

Conceptual
Client
- rcvr = new Receiver()
- cmd = new Command(rcvr, params)
- inv = new Invoker()
- inv.ExecuteCommand (cmd)
- cmd.Execute()
- cmd.rcvr.Operation(cmd.params)
- State of receiver changed

Scenario specific
Client
- acc = new Account()
- dep = new Deposit(acc, amount)
- atm = new ATM()
- atm.ExecuteCommand(dep)
- dep.Execute()
- dep.acc.SetBalance(dep.amount)
- dep.acc.amount updated

3.5 Reversible operations

Although there are many ways to implement undo/redo, the Command pattern is perhaps the
most popular of all. To be able to revert operations, you need to implement the history of
performed operations. The command history is a stack that contains all executed command
objects along with related backups of the application’s state.

We can adapt the previous example:

Copyright: PJ Blignaut, 2022


11

Command interface

The Command interface includes an UnExecute as well.

public interface ICommand


{
void Execute();
void UnExecute();
}

Commands

public class Deposit : ICommand


{
...
public void UnExecute() { account.SetBalance(account.Balance - amount); }
} //class Deposit

public class Withdraw : ICommand


{
...
public void UnExecute() { account.SetBalance(account.Balance + amount); }
} //class Withdraw

Invoker

The invoker now has a private stack to keep track of the commands and also an Unexecute
method. Commands are pushed on the stack when they are executed. On UnExecute, a
command is unexecuted and then popped from the stack.

public class ATM


{
//Stack of commands to keep history
private Stack<ICommand> History = new Stack<ICommand>();

//Execute command
public void ExecuteCommand(ICommand command)
{
command.Execute();
History.Push(command);
} //ExecuteCommand

//Undo command
public void UnExecuteCommand()
{
if (History.Count > 0)
History.Pop().UnExecute();
} //Undo
} //class ATM

3.6 Full-fledged Redo/Undo

It is left as an exercise to adapt the above example further to accommodate a full-fledged


undo/redo application.

Hints:
• Replace the stack with a list of commands.
• Keep track of the current index

Copyright: PJ Blignaut, 2022


12

3.7 Other examples

• https://refactoring.guru/design-patterns/command/csharp/example#lang-features
• https://refactoring.guru/design-patterns/command
• https://code-maze.com/command/
• https://www.dofactory.com/net/command-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/command-design-pattern-dotnet
• https://www.c-sharpcorner.com/UploadFile/851045/command-design-pattern-in-C-Sharp/
• Exam 1, 2021, Question 5 (ATM)
• Test 2, 2021, Question 4 (TV & remote)
• Project 6, 2021 (TV and Remote)
• Project 7, 2022 (List with commands)
• Poject 6, 2023 (Media player)

3.8 Pros and cons

Advantages

• Single Responsibility Principle. You can decouple classes that invoke operations from
classes that perform these operations.
• Open/Closed Principle. You can introduce new commands into the app without breaking
existing client code.
• You can implement undo/redo.
• You can implement deferred execution of operations.
• You can assemble a set of simple commands into a complex one.

Disadvantage

• The code may become more complicated because of an extra layer between senders and
receivers.
• Since the commands live outside the receiver class, members that could otherwise be private
must now be public to make them accessible from the commands. This may lead to lower
security. One could solve this through the use of parameters, but then the system becomes
even more complex.

3.9 When to use

• Since, the Command pattern can turn a specific method call into a stand-alone object, it can
be used to parameterize objects. This allows the passing of commands as method arguments,
storing them inside other objects, switching linked commands at runtime, etc.
• Use the pattern to queue operations, schedule their execution, or execute them remotely.
• Use the Command pattern to implement reversible operations.

3.10 Further reading

• Cardoso, p 217
• Cooper, Chapter 22
• Freeman et al., Chapter 6, p 191
• Gamma, Helm, Johnson, Vlissides (1995, p 299)
• Martin & Martin (2006), Chapter 21

Copyright: PJ Blignaut, 2022


13

• Nesteruk (2019, p 196)


• Okhravi, C. 2017.
https://www.youtube.com/watch?v=9qA5kw8dcSU&list=PLrhzvIcii6GNjpARdnO4ueTU
AVR9eMBpc&index=7
• Shvets, p 269

4. Interpreter pattern

This pattern is left for self-study. You can look at the following sources for a start:

• https://www.geeksforgeeks.org/interpreter-design-pattern/
• https://www.tutorialspoint.com/design_pattern/interpreter_pattern.htm
• https://sourcemaking.com/design_patterns/interpreter
• https://dofactory.com/net/interpreter-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/interpreter-design-pattern-c-sharp
• Cooper, Chapter 23
• Gamma, Helm, Johnson, Vlissides (1995, p 314)
• Nesteruk (2019, p 210)

5. Iterator pattern

5.1 Introduction

The Iterator pattern allows traversal through a list or collection of data using a standard
interface without having to know the details of the internal representations of that data.

.Net has an enumerator feature that can do this (cf Section 5.5), but with the Iterator pattern,
you can also define special iterators that return elements in reversed order or perform some
special processing and return only specified elements of the data collection.

It is important to note that the collection class is separated from the iterator class. The collection
class declares methods to add and remove items from the collection. The iterator class contains
methods to traverse the collection, for example First, Next, Previous, Current, etc.

Copyright: PJ Blignaut, 2022


14

5.2 Conceptual class diagram

Figure 5. Iterator pattern – class diagram

1. The Iterator interface declares the operations required for traversing a collection: fetching
the next element, retrieving the current position, restarting iteration, etc.
2. Concrete iterators implement specific algorithms for traversing a collection. The iterator
object should track the traversal progress on its own. The iterator class also contains a
reference to the collection on which the iterator methods operate.
3. The Collection interface declares one or multiple methods for getting iterators compatible
with the collection. Note that the return type of the methods must be declared as the iterator
interface. The Collection interface also declares methods to add and remove items from the
collection.
4. Concrete collections return new instances of a particular concrete iterator class.
5. The Client works with both collections and iterators via their interfaces. Typically, clients
don’t create iterators on their own, but instead get them from collections.

5.3 Example

Iterable collection interface

• This interface defines the basics of a minimal collection.


• It should be possible to add an item to a collection, return the number of items and retrieve
an item by index. Of course, methods such as Clear, Insert, Remove, RemoveAt, Contains
are also standard, but left out here for the purposes of the discussion.
• The interface is generic to allow data of any type to be contained in the collection.
• The Iterator pattern further expects a CreateIterator method.
• The GetEnumerator method is included for comparison purposes as an alternative for the
Iterator pattern.

Copyright: PJ Blignaut, 2022


15

interface IList<T>
{
//Basics
int Count { get; }
void Add(T item);
T this[int index] { get; }

//For the iterator


IIterator<T> CreateIterator();

//Replacement for the iterator pattern


IEnumerator<T> GetEnumerator();
} //IList<T>

Concrete collection

• This class is the implementation of the above interface.


• As all good containers, it has a private data structure in which the elements are stored, e.g.
an array.
• The array has more cells than is currently in use. Therefore, we cannot use the
array.Length property to return the number of elements in the container. The Count
property keeps track of the number of used elements in the array.
• The constructor instantiates an array with a small number of empty cells and initialises the
Count property.
• The Add method checks that the array has capacity to add a new element and increases its
size if it is not the case. It assigns the new value to the first unused cell and increases the
Count property.
• Remember that the array is private. The indexer allows public access to individual elements
in the array.

• The CreateIterator method instantiates and returns an instance of the Iterator class.
This object can then be used in the client to step through elements in the array.

Note: One can either decide to send the entire instance through to the iterator (this) or just
the array. In case of the latter, there is no need for an indexer since the iterator can access
the elements though its shallow copy of the array. This approach has the disadvantage that
the iterator do not have access to the Count property in the collection and that iteration will
also include the unused spaces in the array.

• The GetEnumerator method returns an instance of System.Collections.Generic


.IEnumerator<T>. This provides an alternative to the Iterator pattern.

class List<T> : IList<T>


{
//Private container
private T[] array;

//Number of used items in the list


// (Not the same as the number of elements in the array)
public int Count { get; private set; }

//Constructor
public List()
{
array = new T[2];
Count = 0;
} //Constructor

Copyright: PJ Blignaut, 2022


16

//Add
public void Add(T item)
{
if (Count == array.Length) //Array is full
System.Array.Resize(ref array, array.Length * 2);
array[this.Count] = item;
Count++;
} //Add

//Indexer
public T this[int index]
{
get
{
if (index >= 0 && index < Count)
return array[index];
return default;
}
} //Indexer

//For purposes of using an iterator to traverse elements in the array


public IIterator<T> CreateIterator()
{
return new Iterator<T>(this);
} //CreateIterator

//Enumerator
//- This uses System.Collections.Generic and is
// a .Net alternative to the iterator pattern
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
int i = 0;
while (i < Count)
yield return array[i++];
} //GetEnumerator
} //class List

The iterator interface

The Iterator pattern is based on methods First and Next and read-only property Current to
allow a client to access members in the collection.

interface IIterator<T>
{
T First();
T Current { get; }
T Next();
}

Concrete iterator

• This class implements the IIterator<T> interface.


• NB: The class has a data field that references an instance of the collection.
- Alternatively, it can reference only the array in the original collection. See the online
example for this.
• The private index is used to keep track of the current element.
• The constructor initialises the alias to the collection and reset the iterator to the first element.
• The Current property uses the indexer in the collection and returns the element at the index
position from the collection.
• The First and Next methods do two things each: Set the index value and returns the element
at that position.

Copyright: PJ Blignaut, 2022


17

class Iterator<T> : IIterator<T>


{
//Private members
private IList<T> list; //Reference to the collection on which the iterator
//methods are applied
private int index; //Keeps track of current position in the collection

//Constructor
public Iterator(IList<T> list)
{
this.list = list;
index = 0; //Go to first element
} //Constructor

//Navigation
public T Current
{
get
{
return list[index];
}
} //Current

public T First()
{
index = 0;
return list[index];
} //First

public T Next()
{
index++;
return list[index];
} //Next
} //class Iterator

Traversal using the indexer

We do not need an iterator to traverse the elements of a collection. We can simply use the
indexer (if there is one).

class Client
{
static void Main()
{
//Create list
IList<string> lstNames = new List<string>();

//Add names
lstNames.Add("John"); lstNames.Add("Mike");
lstNames.Add("Susan"); lstNames.Add("Sarah");

//Step through names using the indexer


Console.WriteLine("\tUse indexer");
for (int i = 0; i < lstNames.Count; i++)
Console.WriteLine("\t" + lstNames[i]);
Console.WriteLine();

//Exit program
Console.Write("\tPress any key to exit ...");
Console.ReadKey();
} //Main
} //class Client

Copyright: PJ Blignaut, 2022


18

Traversal using the iterator

With the iterator, we do not need to access the list by index. It is important to create the iterator
first as the iterate members (First, Next, Current, etc.) are in a separate class. It is also
important to make sure that you reset the iterator before traversal by calling First().

IIterator<string> iterator = lstNames.CreateIterator();


iterator.First();
while (iterator.Current != null)
{
Console.WriteLine("\t" + iterator.Current);
iterator.Next();
}

Traversal using GetEnumerator

The System.Collections.Generic namespace allows the usage of an enumerator. Why


would one bother to use the Iterator pattern if this is available?

foreach (string name in lstNames)


Console.WriteLine("\t" + name);

Class diagram

Figure 6. Iterator pattern applied on a simple collection class

5.4 Other examples

• https://www.dofactory.com/net/iterator-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/iterator-design-pattern-c-sharp
• https://refactoring.guru/design-patterns/iterator
• Test 3, 2021, Question 4
• Project 7, 2021
• Project 7, 2022

Copyright: PJ Blignaut, 2022


19

5.5 The Iterator in C#

The Iterator design pattern has been deliberately hidden in C# in favour of the simple
IEnumerator / IEnumerable. Notice that these interfaces only support forward iteration – there
is no MoveBack() in IEnumerator. The existence of yield allows you to return elements one
at a time (Nesteruk, 2019, 232).

IEnumerable implements IEnumerator, thus relieving you from implementing Current,


MoveNext, etc. So, you can only implement IEnumerable which will behind the scenes do the
work of IEnumerator. In fact, you don’t even have to do that, you can just add a method public
System.Collections.Generic.IEnumerator<T> GetEnumerator() to the collection class
to enable foreach in the client.

5.6 Pros and cons

Advantages

• Single Responsibility Principle. You can clean up the client code and the collections by
extracting bulky traversal algorithms into separate classes.
• Open/Closed Principle. You can implement new types of collections and iterators and pass
them to existing code without breaking anything.
• You can iterate over the same collection in parallel because each iterator object contains its
own iteration state.
• You can pause an iteration and continue it when needed.

Disadvantages

• Applying the pattern can be an overkill if your app only works with simple collections and
the IEnumerator interface of .Net will suffice.
• Using an iterator may be less efficient than going through elements of some specialized
collections directly.

5.7 When to 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.

5.8 Further reading

• Cooper, Chapter 24, p 417


• Freeman et al., p 315
• Gamma, Helm, Johnson, Vlissides (1995, p 331)
• Nesteruk (2019, p 224)
• Okhravi, C. 2017.
https://www.youtube.com/watch?v=uNTNEfwYXhI&list=PLrhzvIcii6GNjpARdnO4ueTU
AVR9eMBpc&index=16

Copyright: PJ Blignaut, 2022


20

6. Mediator pattern

6.1 Introduction

There are situations when it is not desirable that instances of a class are aware of each other’s
presence or communicate through references. As soon as a reference to another instance is kept,
that object’s lifetime is extended beyond what might originally be desired.

When a large proportion of code has different classes that communicate with one another
through direct references, it becomes problematic. The more each class needs to know about
the methods of another class, the more tangled the class structure can become. Eventually, the
dependencies between classes can become chaotic. This makes the program harder to read and
harder to maintain as any change may affect code in several other classes.

The Mediator pattern is a mechanism for facilitating communication between components. The
Mediator pattern promotes looser coupling between classes and reduces the dependencies
between objects. The mediator class is the only class that has detailed knowledge of the methods
of other classes. Direct communication between the objects is restricted and they are forced to
collaborate only via a mediator object. Classes inform the mediator when changes occur, and
the mediator passes on the changes to any other classes that need to be informed.

Typical scenarios where the mediator pattern can be beneficial, includes:


• At a busy airport, aircraft pilots do not talk directly to each other to determine of a runway
is available. All communication goes through the control tower.
• In a virtual chat room with a number of participants, participants are not allowed to
communicate directly with one another but with the chatroom.

6.2 Conceptual class diagram

Figure 7. Mediator pattern - conceptual

Copyright: PJ Blignaut, 2022


21

1. Components are one or more classes that contain some business logic. Each component has
a reference to a mediator (black association), declared with the type of the mediator interface.
Components are not aware of the actual class of the mediator.
Note: Send and Get in this context refers to the direction of flow of the message parameter
from the perspective of the component. In SendMessage, HandleMessage is called wich
sends a message to the mediator. In GetMessage, an incoming message parameter is
received.

2. The Mediator interface declares methods of communication with components, which usually
include just a single Notify or Send method. (I prefer to refer to this method as Relay or
Handle since it gets a message and passes it on.) Components may pass any context as
arguments of this method, including their own objects, but only in such a way that no
coupling occurs between a receiving component and the sender’s class.

3. Concrete Mediators keep references to all components they manage (blue associations).

4. The Client has references to all mediators and optionally to all and components (red
associations). The client may (i) call the SendMessage of a component which will in turn (ii)
call the HandleMessage method of the mediator which will in turn (iii) call the GetMessage
of some or all other components (green curved arrows).

Components must not be aware of other components. If something important happens within
or to a component, it must only notify the mediator. When the mediator receives the notification,
it identifies the sender, and decides what (other) component should be triggered in return. From
a component’s perspective, it all looks like a total black box. The sender does not know who
will end up handling its request, and the receiver does not know who sent the request in the first
place unless the mediator reveals it.

6.3 Example (Air traffic controller)

Components

• Each component has a reference to a mediator declared as IMediator.

public class Airplane


{
private IMediator atc;

public string Registration { get; }

public Airplane(string registration, IMediator atc)


{
this.Registration = registration;
this.atc = atc;
} //Constructor

public void RequestRunway() //Comms out


{
atc.Notify(this);
} //RequestRunway

public void GetMessage(string msg) //Comms in


{
Console.WriteLine("\t" + Registration + " received message: " + msg);
} //Message
} //class Airplane

Copyright: PJ Blignaut, 2022


22

IMediator interface

• The mediator interface declares methods of communication with components, which usually
include just a single notiofication method.

public interface IMediator


{
void Notify(Airplane sender);
} //interface IMediator

Concrete Mediator

public class ATC : IMediator


{
// The mediator must keep reference to all components
private Queue<Airplane> queue = new Queue<Airplane>();

//Timer to simulate intervals between landings


private Timer timer;

//Constructor
public ATC()
{
timer = new Timer(5000);
timer.Elapsed += Timer_Elapsed;
timer.Start();
} //Constructor

//Mediator receives request from plane to use the runway


public void Notify(Airplane sender)
{
Console.WriteLine("\tATC receives request from " + sender.Registration);
queue.Enqueue(sender); //Normally, this is done in the component's
constructor, but since the queue changes with new
arrivals and landings, it is moved here.
} //Notify

//Send notifications to all planes in the queue


private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (queue.Count > 0)
{
Console.WriteLine(); //Just for readability

//Permission for first plane in the queue for landing


Airplane plane1 = queue.Dequeue();
plane1.GetMessage("Runway is available for landing.");

//Notify other planes that the runway is not available


foreach (Airplane plane in queue)
plane.GetMessage("Runway is not available");

Console.WriteLine(); //Just for readability


} //if
else
Console.WriteLine("\tRequest queue is empty");
} //Timer_Elapsed

} //class ATC

Copyright: PJ Blignaut, 2022


23

Client

• The mediator and all components are instantiated.


• The components must know who the mediator is, and the mediator must have a list of all
components. This linking is done in the component's constructor.
• Components have a SendMessage method (RequestRunway in this example) which in turn
calls the mediator's Send/Notify method. Components do not know who the other
components are and communicate only with the mediator.
• The client may refer to the mediator directly, i.e. not through a component.

class Client
{
static void Main(string[] args)
{
//Mediator
IMediator atc = new ATC();

//Instantiate planes
//- Components must not be aware of other components
Airplane plane1 = new Airplane("ABC", atc);
Airplane plane2 = new Airplane("DEF", atc);
Airplane plane3 = new Airplane("GHI", atc);
Airplane plane4 = new Airplane("JKL", atc);
Airplane plane5 = new Airplane("MNO", atc);

//Three planes request use of runway


plane1.RequestRunway();
plane2.RequestRunway();
plane3.RequestRunway();

//After a while, more planes join the air space of the airport and request
to land
Thread.Sleep(8000);
plane4.RequestRunway();
plane5.RequestRunway();

// Wait for user


Console.ReadKey();
} //Main
} //class Client

Copyright: PJ Blignaut, 2022


24

Class diagram

6.4 Example (Angry parents)

Consider the (rather awkward) example of a father and mother who are angry at each other.
They don't speak to each other directly and all communication is through a child who is sitting
at the same table.

In this example, Father and Mother are subclasses of AParent and serve as components. The
Child class is the mediator.

Components

• The components may or may not belong to the same class. If not, we need an abstract class
with the mediator details and then derived classes with specific behaviours. In this example,
we let the Father and Mother classes (components) respond differently on a message from
the child (mediator).
• Every component object (father and mother) must have a reference to the mediator
(child).
• The constructor does a two-way connection:
- The component gets information about the mediator.
- The mediator gets information about the component.
• The SendMessage method calls the mediator's Send (or Invoke) method. The mediator
will, in turn, relay the message to other components.
• In this example, there are two overloaded GetMessage methods. Both messages are called
from the mediator. The first version identifies the sender (a component) and the second one
identifies the mediator.

Copyright: PJ Blignaut, 2022


25

abstract class AParent


{
//Component data fields and properties
public string Name { get; }

//Each component has a reference to a mediator


private IMediator child;

//Constructor
public AParent(string name, IMediator child)
{
this.Name = name;
this.child = child; //Tell the component who the mediator is
child.AddParent(this); //Tell the mediator to add this component to its
list of components for which it must care
} //Constructor

//Comms out - send to mediator who will relay the message further
public void SendMessage(string message)
{
child.RelayMessage(this, message);
}

//Comms in
public void GetMessage(AParent sender, string message)
{
string sMsg = string.Format("\t" + child.Name + ": \"{0}, {1} says '{2}'\"",
this.Name, sender.Name, message);
Console.WriteLine(sMsg);
} //GetMessage

//Comms in
public void GetMessage(IMediator child, string message)
{
string sMsg = string.Format("\t" + child.Name + ": \"{0}, {1}\"",
this.Name, message);
Console.WriteLine(sMsg);
} //GetMessage

public abstract void Responds();


} //class AParent

class Father : AParent


{
public Father(string name, IMediator child) : base(name, child) { }
public override void Responds() { Console.WriteLine("\t" + Name + " grunts."); }
} //class Father

class Mother : AParent


{
public Mother(string name, IMediator child) : base(name, child) { }
public override void Responds() { Console.WriteLine("\t" + Name + " cries."); }
} //class Mother

IMediator interface

• The essential member in this interface is the RelayMessage (aka Invoke or Notify) method.
It allows components to send a message to the mediator. The mediator will then relay the
message to other components.
• The first overloaded RelayMessage method is used to identify the sending component. The
second one can be used by the client to send a message.

Copyright: PJ Blignaut, 2022


26

• The AddParent method is used in the component's constructor to add the component to the
list of components for the mediator.

interface IMediator
{
string Name { get; }
void AddParent(AParent parent);
void RelayMessage (AParent sender, string msg);
void RelayMessage (string msg);
} //interface IMediator

Concrete Mediator

• This class implements the IMediator class above.


• Note the two overloaded implementations of Send. The first one relays the message to all
components excluding the sender. The second one does not identify the sender and can also
be invoked by the client code or the mediator itself.

class Child : IMediator


{
//General properties of the mediator if necessary
public string Name { get; }
//The mediator must keep reference to all components
private List<AParent> lstParents;

//Constructor
public Child(string name)
{ this.Name = name;
lstParents = new List<AParent>();
} //Constructor

//Enable the components to add themselves to the mediator's list


public void AddParent(AParent parent)
{ lstParents.Add(parent);
} //AddParent

//Mediator receives request from component to relay to all other components


public void RelayMessage(AParent sender, string message)
{
AParent receiver = null;
foreach (AParent parent in lstParents)
if (parent != sender)
receiver = parent;

string sMsg = string.Format("\t" + sender.Name + ": \"{0}, tell {1}, '{2}'\"",


this.Name, receiver.Name, message);
System.Console.WriteLine(sMsg);
receiver.GetMessage(sender, message);
} //Send

//Mediator receives request from client and send message to all components
public void RelayMessage(string message)
{
foreach (AParent parent in lstParents)
{ parent.GetMessage(this, message);
parent.Responds();
} //foreach
} //Send
} //class Child

Copyright: PJ Blignaut, 2022


27

Client

• The mediator and all components are instantiated.


• The components must know who the mediator is, and the mediator must have a list of all
components. This linking is done in the component's constructor.
• Components have a SendMessage method which in turn calls the mediator's Send method.
Components do not know who the other components are and communicate only with the
mediator.
• The client may refer to the mediator directly, i.e. not through a component.

class Client
{
static void Main(string[] args)
{
//Create mediator
IMediator child = new Child("Johnny");

//Create components with information about the mediator


AParent father = new Father("Dad", child);
AParent mother = new Mother("Mom", child);

//Communication through the mediator


father.SendMessage("Pass me the jam.");
mother.SendMessage("Thank you.");
child.RelayMessage("Stop bickering!!");

//Wait for user


Console.Write("\n\tPress any key to exit");
Console.ReadKey();
} //Main
} //class Client

Note: In this example, the client initiates the communication from the components, but
actually, the components should initiate the messages themselves.

Copyright: PJ Blignaut, 2022


28

Class diagram

Figure 8. Mediator pattern as applied on a kitchen conversation

6.5 Use event handlers for the output

It is never a good idea to include Console.Write statements in service classes. Formatting of


output should be done by a client class. The above example can be adapted to use the .Net event
handling mechanisms to invoke an event in the service classes that can be handled in the client
class. The text formatting is then done in the event handler and no longer in the service class.

Remember the five steps as taught in the previous semester:

Service class
1. Declare the delegates
2. Declare the events
3. Invoke the events

Client class
4. Subscribe an event handler to the event
5. Handle the event

Components

Compare the code below with the previous example.

• Step 1: Add two delegates (method types) – one for a general message and one for a message
with identifying sender, receiver and mediator.

delegate void delOnMessage(string msg);


delegate void delOnComponentMessage(AParent sender, AParent receiver,
IMediator child, string msg);

• Step 2: Add events for the component classes

Copyright: PJ Blignaut, 2022


29

• Step 3: Invoke the events when output must be generated.


abstract class AParent
{
...
//Step 2: Declare the event
public event delOnComponentMessage OnComponentMessage;

...

//Comms in
public void GetMessage(AParent sender, string message)
{
//Step 3: Invoke the event
OnComponentMessage?.Invoke(sender, this, child, message);
} //GetMessage

//Comms in
public void GetMessage(string message)
{
//Step 3: Invoke the event
OnComponentMessage?.Invoke(null, this, child, message);
} //GetMessage

public abstract void Responds();


} //class AParent

//If there are more than one type of component


class Father : AParent
{
public Father(string name, IMediator child) : base(name, child) { }
public event delOnMessage OnMessage; //Step 2

public override void Responds()


{
string message = Name + " grunts.";
OnMessage?.Invoke(message); //Step 3
}
} //class Father

class Mother : AParent


{
...
} //class Mother

IMediator interface

delegate void delOnMediatorMessage(AParent sender, AParent receiver,


IMediator child, string msg);

interface IMediator
{
string Name { get; }
void AddParent(AParent parent);
void RelayMsg(AParent sender, string msg);
void RelayMsg (string msg);
event delOnMediatorMessage OnMediatorMessage;
} //interface IMediator

Copyright: PJ Blignaut, 2022


30

Concrete mediator

class Child : IMediator


{
...
public event delOnMediatorMessage OnMediatorMessage;

...

public void RelayMsg (AParent sender, string message)


{
AParent receiver = null;
foreach (AParent parent in lstParents)
if (parent != sender)
receiver = parent;

OnMediatorMessage?.Invoke(sender, receiver, this, message);


receiver.GetMessage(sender, message);
} //Send

...
} //class Child

Client

• Step 4: Subscribe
• Step 5: Handle. The text formatting is done in the event handler in the client class and
no longer in the service class.
• Note that we can do Step 4 and 5 together with an anonymous method if the handler is
short and simple.

class Client
{
static void Main(string[] args)
{
//Create mediator
IMediator child = new Child("Johnny");
child.OnMediatorMessage += OnMediatorMessage; //Step 4: Subscribe

//Create components with information about the mediator


Father father = new Father("Dad", child);
father.OnComponentMessage += OnComponentMessage; //Step 4: Subscribe
father.OnMessage += delegate (string msg)
{ Console.WriteLine("\t" + msg);
}; //Step 4: Subscribe and handle

Mother mother = new Mother("Mom", child);


mother.OnComponentMessage += OnComponentMessage; //Step 4: Subscribe
mother.OnMessage += delegate (string msg)
{ Console.WriteLine("\t" + msg);
}; //Step 4: Subscribe and handle

//Communication through the mediator


father.SendMessage("Pass me the jam.");
mother.SendMessage("Thank you.");
child.RelayMsg("Stop bickering!!");

//Wait for user


Console.Write("\n\tPress any key to exit");
Console.ReadKey();
} //Main

Copyright: PJ Blignaut, 2022


31

//Step 5: Handle
private static void OnMediatorMessage(AParent sender, AParent receiver,
IMediator child, string message)
{
string msg = string.Format("\t" + sender.Name + ": \"{0}, tell {1}, '{2}'\"",
child.Name, receiver.Name, message);
Console.WriteLine(msg);
}

//Step 5: Handle
private static void OnComponentMessage(AParent sender, AParent receiver,
IMediator child, string message)
{
string msg = "";
if (sender != null)
msg = string.Format("\t" + child.Name + ": \"{0}, {1} says '{2}'\"",
receiver.Name, sender.Name, message);
else
msg = string.Format("\t" + child.Name + ": \"{0}, {1}\"",
receiver.Name, message);
Console.WriteLine(msg);
} //OnComponentMessage
} //class Client

6.6 Other examples

• https://www.dotnettricks.com/learn/designpatterns/mediator-design-pattern-c-sharp
• https://www.dofactory.com/net/mediator-design-pattern
• https://refactoring.guru/design-patterns/mediator
• Exam 1, 2020, Question 6 (Chat room)
• Exam 2, 2021, Question 5 (ATC)
• Project 8, 2022 (Real estate agent)
• Project 7, 2023 (Maps)

6.7 Pros and cons

Advantages

• Single Responsibility Principle. You can extract the communications between various
components into a single place, making it easier to comprehend and maintain.
• Open/Closed Principle. You can introduce new mediators without having to change the
actual components.
• You can reduce coupling between various components of a program.
• You can reuse individual components more easily.

Disadvantage

• Over time a mediator can evolve into a god object. That means that the mediator has too
much power and knows too much about the component classes.

6.8 When to use

• Use the Mediator pattern when it is not desirable that instances of a class are aware of each
other’s presence or communicate through references
• Use the Mediator pattern when it is hard to change some of the classes because they are
tightly coupled to a bunch of other classes.
Copyright: PJ Blignaut, 2022
32

• Use the pattern when you cannot reuse a component in a different program because it is too
dependent on other components.
• Use the Mediator when you find yourself creating tons of component subclasses just to
reuse some basic behaviour in various contexts.

6.9 Further reading

• Cooper, Chapter 25, p 427


• Gamma, Helm, Johnson, Vlissides (1995, p 351)
• Martin & Martin (2006), Chapter 23, p 418
• Nesteruk (2019, p 234)
• Shvets, p 304

7. Memento pattern

7.1 Introduction

Sometimes it is necessary to record the internal state of an object. This is required when
implementing checkpoints and undo mechanisms that let users back out of tentative operations
or recover from errors. You must save state information somewhere so that you can restore
objects to their previous states. But objects normally encapsulate some or all of their state,
making it inaccessible to other objects and impossible to save externally. Exposing this state
would violate encapsulation, which can compromise the application’s reliability and
extensibility.

We can solve this problem with the Memento pattern. A memento is an object that stores a
snapshot of the internal state of another object – the memento’s originator. The undo
mechanism will request a memento from the originator when it needs to checkpoint the
originator’s state. The originator initializes the memento with information that characterizes its
current state. Only the originator can store and retrieve information from the memento – the
memento is “opaque” to other objects.

7.2 Operational details

The Memento pattern allows saving and restoring the previous state of an object without
revealing the details of its implementation.

The memento pattern is implemented with three objects, namely the originator, a caretaker,
and a memento.

• The Originator class is the class of objects that we want to keep history of. It has some
internal state fields, i.e. data fields and properties. It has methods to return its current state
as a Memento and update its state based on the state of a given memento.
• The Memento class has the same data fields and properties (state) as the Originator, but not
the behaviours (methods). The memento should be immutable – i.e. it should not be possible
to change its state after construction.
• The Caretaker is going to do something to the originator but wants to be able to undo the
change. The caretaker may be the client class. The caretaker contains a list or stack of
mementos. Before any operation, the caretaker gets a memento object and adds it to the list.
The caretaker can undo an operation or reverting the originator to a previous version by
retrieving a memento from the list.

Copyright: PJ Blignaut, 2022


33

There are three possible implementations of the Memento pattern:

• If the programming language does not allow nested classes, it is important to ensure that the
caretaker cannot access the memento's data fields. This is normally done by using an
explicitly declared intermediate interface.
• It is important to ensure that other classes cannot change state of the originator through the
memento. This can be done by adding an interface which the originator must implement.
• The Memento class can be nested inside the Originator class. This allows the originator to
access the fields inside the memento, even though they are declared private, but the caretaker
(client) would not be able to tamper with its state. Since C# allows nested classes, this is the
approach that we will follow.

7.3 Conceptual class diagram

Figure 9. Memento pattern - conceptual

1. The Originator class can produce snapshots of its own state (SaveToMemento), as well as
restore its state from snapshots when needed (RestoreFromMemento).
2. The Memento is a value object that acts as a snapshot of the originator’s state. It’s a common
practice to make the memento immutable and pass it the data only once, via the constructor.
3. A caretaker keeps track of the originator’s history by storing a stack of mementos. When the
originator has to travel back in history, the caretaker fetches the topmost memento from the
stack and passes it to the originator’s restoration method.
4. In this implementation, the memento class is nested inside the originator. The set part of all
properties is private so that the caretaker or any other class cannot change the data.

7.4 Example with Client as the caretaker

Consider the example of a Student class which keeps track of a student's student number, name
and modules for which he/she is registered.

Originator

There is nothing unfamiliar here. This is the class for which we want to keep track of its state
over its history of changes.
class Student
{

Copyright: PJ Blignaut, 2022


34

//State fields
public string StudentNumber { get; private set; }
public string Name { get; private set; }
public List<string> Modules { get; private set; }

//Constructor
public Student(string studentNumber, string name)
{
this.StudentNumber = studentNumber;
this.Name = name;
Modules = new List<string>();
} //Constructor

//Manage modules
public void AddModule(string moduleCode) { Modules.Add(moduleCode); }
public void RemoveModule(string moduleCode) { Modules.Remove(moduleCode); }
public List<string> GetModules() { return Modules; }

//ToString
public override string ToString()
{
return StudentNumber + ", " + Name + ", [" + string.Join(", ", Modules) + "]";
}

#region Memento class and Memento related operations


...
#endregion Memento
} //Student

Memento

The Memento class is nested inside the Originator class in the region as indicated above.

• The data fields are the same as for the Originator class. Together, they are referred to as
the saved state.
• The data fields are immutable. They cannot be changed after construction of the object.
• The constructor assigns values to the state fields.
• Note that SaveToMemento and RestoreFromMemento are methods of the Originator class.
• Note that we should not make shallow copies of fields with reference types. That would
mean that the saved state is just an alias for the originator's fields and will change if the
originator's state is changed.

Copyright: PJ Blignaut, 2022


35

public class Memento


{
//Saved state
public string StudentNumber { get; private set; }
public string Name { get; private set; }
public List<string> Modules { get; private set; }

//Memento constructor
public Memento(string studentNumber, string name, List<string> modules)
{
this.StudentNumber = studentNumber;
this.Name = name;
//this.Modules = modules; Wrong!!!
this.Modules = new List<string>(modules); //Must be deep copy.
} //Constructor
} //class Memento

//In class Student


public Memento SaveToMemento()
{
Memento memento = new Memento(StudentNumber, Name, Modules);
return memento;
} //SaveToMemento

public void RestoreFromMemento(Memento memento)


{
this.StudentNumber = memento.StudentNumber;
this.Name = memento.Name;
//this.Modules = memento.Modules; //Wrong !!!
this.Modules = new List<string>(memento.Modules); //Must be deep copy
} //RestoreFromMemento

Client

If we have a single object to be taken care of, we can use the client as the caretaker.

• The caretaker has a list of saved states. The list contains objects of the Memento class which
is nested inside Student.
• Every operation (AddModule, RemoveModule) must be preceded with a call to
SaveToMemento.
• In this example, there is no safety net in case the client enters an invalid index when restoring
from the memento's. It is left to you as an exercise.

class Client
{
static void Main(string[] args)
{
//Caretaker maintains a list of mementos
List<Student.Memento> savedStates = new List<Student.Memento>();

//New student with state


Student student = new Student("2020123456", "Johnny Mickelson");
student.AddModule("CSIS2614"); student.AddModule("CSIS2634");
student.AddModule("CSIS2624"); student.AddModule("CSIS2664");

//Display original student


Console.WriteLine("\t" + student); //2614, 2634, 2624, 2664

//Remove module and display student


savedStates.Add(student.SaveToMemento()); //Get current state before operation
student.RemoveModule("CSIS2614");
Console.WriteLine("\t" + student); //2634, 2624, 2664

Copyright: PJ Blignaut, 2022


36

//Add module and display student


savedStates.Add(student.SaveToMemento()); //Get current state before operation
student.AddModule("CSIS2654");
Console.WriteLine("\t" + student); //2634, 2624, 2664, 2654

//Restore student and display again


student.RestoreFromMemento(savedStates[savedStates.Count - 1]);
Console.WriteLine("\t" + student); //2634, 2624, 2664,

//Wait for user


Console.Write("\n\tPress any key to exit ... "); Console.ReadKey();
} //Main
} //class Client

• Output

7.5 Example with separate caretaker class

When we have a list of objects, each one with its own list of saved states, things can become
messy in the Client class. In any case, we do not want the client to take care of safe restores.
If we adapt the above example to make provision for more than one student, it would be better
to declare a separate caretaker class.

Caretaker class

• The caretaker class encapsulates the actual object for which we have to keep track of state.
• We use a Stack<Memento> to keep track of the history.
• Note that the current state must be pushed on the stack before a new operation is executed.
• The Restore method has a step parameter to indicate how far we must go back in history.
We can only pop from the stack if it is not empty.
• It is left as an exercise to also implement a redo stack.
• The history can quickly become very large. It is recommended to use a sliding window of
history so that, for example, only the last 10 versions are kept on the stack. You can try to
do that.

class ctStudent
{
//Private fields for the Originator and stack of mementos
private Student student;
private Stack<Student.Memento> history = new Stack<Student.Memento>();

//Constructor
public ctStudent(Student student) { this.student = student; }

//ToString
public override string ToString() { return student.ToString(); }

Copyright: PJ Blignaut, 2022


37

//Operations
public void AddModule(string module)
{
history.Push(student.SaveToMemento()); //Save current state before operation
student.Modules.Add(module);
}

public void RemoveModule(string module)


{
history.Push(student.SaveToMemento()); //Save current state before operation
student.Modules.Remove(module);
}

//Restore to a previous state


public void Restore(int steps)
{
for (int step = 1; step <= steps; step++)
if (history.Count > 0)
student.RestoreFromMemento(history.Pop());
}
} //class ctStudent

Client

The Client class of the previous example must now be adapted.

• The client does not take responsibility for maintaining lists of saved states.
• The client works with a list of caretakers of students (List<ctStudent>), not a list of
students (List<Student>).
• It would actually be better to create a container class for Students.

class Client
{
static void Main(string[] args)
{
//List of students
List<ctStudent> Students = new List<ctStudent>();
//New student with state
Student student = new Student("2020123456", "Johnny Mickelson");
Students.Add(new ctStudent(student));

//Add modules
Students[0].AddModule("CSIS2614"); Students[0].AddModule("CSIS2634");
Students[0].AddModule("CSIS2624"); Students[0].AddModule("CSIS2664");
Console.WriteLine("\t" + student); //2614, 2634, 2624, 2664

//Change student and display


Students[0].RemoveModule("CSIS2614");
Console.WriteLine("\t" + Students[0]); //2634, 2624, 2664

//Change student and display


Students[0].RemoveModule("CSIS2624");
Console.WriteLine("\t" + Students[0]); //2634, 2664

//Restore student to a previous state and display again


Students[0].Restore(-2); //Reverse the last two operations
Console.WriteLine("\t" + Students[0]); //2614, 2634, 2624, 2664

//Wait for user


Console.Write("\n\tPress any key to exit ... "); Console.ReadKey();
} //Main
} //class Client

Copyright: PJ Blignaut, 2022


38

Class diagram

Figure 10. Memento pattern as applied on student application

7.6 Other examples

Note that not all these examples follow the exact same pattern.

• https://www.c-sharpcorner.com/UploadFile/dacca2/design-pattern-for-beginner-part-8-
memento-design-patter/
• https://www.dofactory.com/net/memento-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/memento-design-pattern-c-sharp
• https://refactoring.guru/design-patterns/memento
• Project 8, 2021 (Text editor, version control)
• Exam 2, 2021, Question 6 (Text editor, version control)
• Project 8, 2023 (Sorting)

7.7 Pros and cons

Advantages

• You can produce snapshots of the object’s state without violating its encapsulation.
• Through a history of snapshots, it is possible to restore an object to an earlier state.
• You can simplify the originator’s code by letting the caretaker maintain the history of the
originator’s state.

Disadvantages

• The app might consume lots of RAM if clients create mementos too often.
• Caretakers should track the originator’s lifecycle to be able to destroy obsolete mementos.
• Most dynamic programming languages, such as PHP, Python and JavaScript, can’t guarantee
that the state within the memento stays untouched.
Copyright: PJ Blignaut, 2022
39

7.8 When to 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.

The Memento pattern lets you make full copies of an object’s state, including private
fields, and store them separately from the object.

• Use the pattern when direct access to the object’s fields/getters/ setters violates its
encapsulation.

The Memento makes the object itself responsible for creating a snapshot of its state. No
other object can read the snapshot, making the original object’s state data safe and secure.

7.9 Further reading

• https://en.wikipedia.org/wiki/Memento_pattern
• Cooper, Chapter 26, 441
• Gamma, Helm, Johnson, Vlissides (1995, p 365)
• Nesteruk (2019, p 244)
• Shvets, p 326
• Banas, D. https://www.youtube.com/watch?v=jOnxYT8Iaoo

8. Null object pattern

8.1 Introduction

A null value is an indication of nothingness. It is not the same as an empty value. It means that
the compiler is aware of the variable, but no memory cell is allocated. Of the following three
initialisations, the first two are equivalent, but they are fundamentally different from the third:

string s;
string s = null;
string s = "";

A null object is also known as a stub, an active nothing or an active null. It is not nothing, but
an instance that does nothing. Instead of using a null reference to convey the absence or non-
existence of an object, the Null Object pattern uses an object which implements the expected
interface, but whose method bodies are empty. The advantage of this approach is that a null
object is very predictable and has no side effects: it does nothing.

With the Null Object pattern, a null object replaces the need to check for null. In other words,
instead of using null to reflect the notion of nothing, an instance is created with default
behaviour that does nothing. Note that this does not eliminate the null error due to careless
programming. Instead, it allows a careful programmer with an alternative to represent non-
existence.

Copyright: PJ Blignaut, 2022


40

8.2 Conceptual class diagram

Figure 11. Null object pattern - conceptual

1. The abstract class declares the common behaviours.


2. The concrete classes include a null object class. This class implements the abstraction in
the same way as the other concrete classes, except that the method bodies are empty.
3. The client interacts with the abstraction.

8.3 Example

Consider a program where we have animals of various types.

Figure 12. Example Null Object pattern

Abstract class

• The abstraction of animals contains all the common attributes and methods.

abstract class AAnimal


{
public abstract void MakeSound();
} //abstract class AAnimal

Concrete animals

• The derived classes implement the abstract class with specific details in the overridden
methods.
• A NullAnimal class is included that inherits from AAnimal in the same way that concrete
animals do with the exception that the method implementations have empty bodies.

Copyright: PJ Blignaut, 2022


41

• Note that the NullAnimal class is sealed. There is no point in inheriting from an object that
deliberately has no behaviour.

class Dog : AAnimal


{
public override void MakeSound() { System.Console.WriteLine("\tWoof woof!"); }
} //class Dog

class Cat : AAnimal


{
public override void MakeSound() { System.Console.WriteLine("\tMeow!"); }
} //class Dog

sealed class NullAnimal : AAnimal


{
public override void MakeSound() { }
}

Client

Suppose the client would, for some or other reason, like to refer to a non-existent animal.
Consider the following three ways of initialising such non-existence with the respective
consequences:

1. No initialisation - Compiler error:

2. Null assignment - Run-time error:

3. Null object assignment - no error. The MakeSound method does nothing but does also not
give an error.

AAnimal animal = new NullAnimal();


animal.MakeSound();

Copyright: PJ Blignaut, 2022


42

8.4 Alternative implementation

Since C# allows nested classes, we can declare a private NullAnimal class inside the abstract
class.

abstract class AAnimal


{
public static AAnimal Null = new NullAnimal();
private sealed class NullAnimal : AAnimal
{
public override void MakeSound() { }
}

public abstract void MakeSound();


} //abstract class AAnimal

• Instantiating a null animal from the client will then be done like this:

IAnimal animal = AAnimal.Null;

Figure 13. Usage of alternative Null Object pattern

8.5 Using the Null Object for a simple class

It is not necessary to have an abstract class with several possible descendants. We can also
provide for a Null object for a simple class.

• It is important that the class developer specifies the behaviour of a Null object. In the
example below, the ToString() method is overridden to return an empty string.

class Car
{
//Properties
public string make { get; private set; }
public string model { get; private set; }
public string registration { get; private set; }

//Constructors
public Car() { }
public Car(string make, string model, string registration)
{
this.make = make;
this.model = model;
this.registration = registration;
}

Copyright: PJ Blignaut, 2022


43

//To String
public override string ToString()
{
return string.Join(", ", new string[] { make, model, registration });
}

#region Null object


public static Car Null = new NullCar();
private sealed class NullCar : Car
{
public override string ToString() { return ""; }
} //NullCar
#endregion Null object

• The following client code would print an empty string:


Car car = Car.Null;
Console.WriteLine(car);

8.6 Pros and cons

Advantages

• Null objects can be used in place of real objects when the object is expected to do nothing.
• The Null Object pattern makes the client code simple. Clients can treat real objects and null
objects in the same way. This simplifies client code, because it avoids having to write testing
code which handles null objects specially.
• The behaviour of a null object is very predictable and has no side effects: it does nothing.
• The null object pattern helps us to write a clean code and avoid null checks.
• Using the Null Object pattern, callers do not have to care whether they have a null object or
a real object.

Disadvantages

• The Null Object pattern can be difficult to implement if various clients do not agree on how
the null object should do nothing if the abstraction is not well defined.
• To be consistent, application of the Null Object pattern can necessitate creation of a null
object class for every abstract class in the program.
• It is not possible to implement the Null Object pattern in every scenario. Sometimes, it is
necessary to work with a null reference and perform some null checks.

8.7 When to use

• Use the Null Object pattern to avoid frequent checks for null.
• Use the Null Object pattern if you want to return an object of the expected type, but do
nothing.

8.8 Further reading

• https://www.c-sharpcorner.com/article/null-object-design-pattern/
• https://www.geeksforgeeks.org/null-object-design-pattern/
• https://www.tutorialspoint.com/design_pattern/null_object_pattern.htm
• https://en.wikipedia.org/wiki/Null_object_pattern
• Nesteruk (2019, p 250)

Copyright: PJ Blignaut, 2022


44

9. Observer pattern

9.1 Introduction

The Observer pattern allows an object to notify other objects that something has happened. This
happens often in the real world. If, for example, somebody has made a payment into your bank
account, you are notified with an SMS.

This approach uses a Push mechanism for communication. The service object pushes a message
with the necessary details to all its subscribers whenever something has happened to it. This is
in contrast with a Pull approach where other objects have to request a service object for
information every now and then. With a Pull approach the requester must determine how often
it should ask. If it asks infrequently, it is possible that happenings may have a nasty consequence
if the requester does not respond quickly enough. If it asks too often, lots of processing power
might be wasted with unnecessary operations.

Because the Observer pattern is popular and necessary, the designers of C# incorporated the
pattern into the language with the use of the event keyword. Events can be members of a class
and are decorated with the event keyword. Event handlers are methods that are called whenever
an event is raised. Refer to Chapter 19 of the previous semester to revise the principles of events
and event handlers in C#.

Since we try to teach you how to program and not to use C#, it is important that you understand
what is happening behind the scenes with the Observer pattern.

9.2 Conceptual diagram

Figure 14. Observer pattern - conceptual

1. The Publisher issues events of interest to other objects. These events occur when the
publisher changes its state or executes some behaviours. Publishers contain a subscription
infrastructure that lets new subscribers join and current subscribers leave the list.

When a new event happens, the publisher goes over the subscription list and calls the
notification method declared in the subscriber interface on each subscriber object.

2. The ISubscriber interface declares the notification interface. In most cases, it consists of a
single Update method. The method may have several parameters that let the publisher pass
some event details along with the update.

Copyright: PJ Blignaut, 2022


45

ISubscriber may optionally also declare Subscribe and Unsubscribe to allow the
subscriber to initiate the subscriptions. These methods will then call the corresponding
methods of the publisher with itself (this) as parameter.

3. Concrete subscribers perform some actions in response to notifications issued by the


publisher. All these classes must implement the same interface, so the publisher is not
coupled to concrete classes.

Usually, subscribers need some contextual information to handle the update correctly. For
this reason, publishers often pass some context data as arguments of the notification method.
The publisher can pass itself as an argument, to allow subscribers to fetch any required data
directly.

4. The Client creates publisher and subscriber objects separately and then registers subscribers
for publisher updates.

9.3 Example

Share prices of a specific commodity fluctuates up and down. Consider the example of brokers
who buy shares of a specific entity when the share price is low and sell shares when the price
is high. Since the brokers do not watch the movements all the time (they have to sleep
sometimes), they have a program that watches the price on their behalf and warns them when
the price reaches a predefined threshold on either end.

Publisher

• The class PriceWatch acts as publisher. It publishes relevant events whenever the share
price exceeds the set limits.
• An infinite loop is used to simulate the process of watching the stock market prices and
notifying subscribers of low or high prices.
• The infinite loop must run in a separate thread or else it will block execution. You can
comment out the thread and see what happens.
• A random number generator is used to simulate a share price.
• Don't use a foreach to iterate through the subscribers during the notification process. The
list of subscribers might change during that period which will cause an error (see below).
Use either a normal for loop or Parallel.ForEach which runs every loop traversal in its
own thread.

class PriceWatch
{
//Thresholds to demarcate when events must be raised
public decimal lowThreshold { get; private set; }
public decimal highThreshold { get; private set; }
//List of subscribers
private List<ISubscriber> lstSubscribers;

Copyright: PJ Blignaut, 2022


46

//Constructor
public PriceWatch(decimal lowThreshold, decimal highThreshold)
{
this.lowThreshold = lowThreshold;
this.highThreshold = highThreshold;
lstSubscribers = new List<ISubscriber>();
} //Constructor

//Subscribe and Unsubscribe subscribers


public void Subscribe(ISubscriber subscr) { lstSubscribers.Add(subscr); }
public void UnSubscribe(ISubscriber subscr) { lstSubscribers.Remove(subscr); }

//Watch the stock market


public void WatchPrice(int interval)
{
//Run the infinite loop in a separate thread or else it will block execution
new Thread(delegate ()
{
//Infinite loop to watch share prices
while (true)
{
//Simulation: System reads current share price from stock market
Random rnd = new Random();
decimal price = rnd.Next(1000);
ClearConsole();
Console.Write("\tCurrent price: " + price.ToString("0.00"));

//Notify subscribers of share price


if (price < lowThreshold)
Parallel.ForEach(lstSubscribers,
subscriber => subscriber.HandleLowPrice(price));

if (price > highThreshold)


for (int i = 0; i < lstSubscribers.Count; i++)
lstSubscribers[i].HandleHighPrice(price); //Invoke

//Wait a bit and then check the price again


Thread.Sleep(interval);
} //while true
}).Start();
} //WatchPrice
} //class PriceWatch

ISubscriber interface

• This interface defines two event handlers which must be implemented by every subscriber.

public interface ISubscriber


{
void UpdateLow(decimal Price);
void UpdateHigh(decimal Price);
}

Concrete subscribers

• There may be many instances of this class.


• Every instance handles the events as they are raised by the publisher. In this example, a
sound is generated, and a message appears in the console window.
Copyright: PJ Blignaut, 2022
47

• The constructor subscribes a broker automatically upon instantiation. This is not necessary
and can also be done explicitly by the client.
class Broker : ISubscriber
{
//Subscriber data fields
public string Name { get; private set; }

//Constructor
public Broker(string name, PriceWatch pw)
{
this.Name = name;
pw.Subscribe(this);
} //Constructor

public void UpdateLow(decimal price)


{
Console.Beep();
Console.Write("\n\t" + Name + ": Low price warning: "
+ price.ToString("C"));
} //UpdateLow

public void UpdateHigh(decimal price)


{
Console.Beep();
Console.Write("\n\t" + Name + ": High price warning: "
+ price.ToString("C"));
} //UpdateHigh
} //class Broker

Client

• The client instantiates the publisher, PriceWatch, and all the subscribers.
• The client also subscribes and unsubscribes brokers periodically.

class Client
{
static void Main(string[] args)
{
//Create publisher
PriceWatch pw = new PriceWatch(100, 900);

//Run price watch in separate thread, else the execution will block.
new Thread(delegate () { pw.WatchPrice(1000); }).Start();

//Define brokers
Isubscriber broker1 = new Broker("John", pw);
Isubscriber broker2 = new Broker("Mike", pw);
Isubscriber broker3 = new Broker("James", pw);

//Periodically, subscribe new brokers and unsubscribe and resubscribe


// existing brokers
Thread.Sleep(5000);
pw.UnSubscribe(broker1);
Thread.Sleep(1000);
Isubscriber broker4 = new Broker("Susan", pw);
Thread.Sleep(2000);
pw.Subscribe(broker1);

//This thread is done, but the program does not terminate


// because the price watcher is still running.
} //Main
} //Client

Copyright: PJ Blignaut, 2022


48

Class diagram

Figure 15. Observer pattern as applied on share price watcher

9.4 Other examples

• https://www.c-sharpcorner.com/UploadFile/dacca2/design-pattern-for-beginner-part-10-
observer-design-patter/
• https://www.dofactory.com/net/observer-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/observer-design-pattern-c-sharp
• https://refactoring.guru/design-patterns/observer
• Project 8 & Exam 2, 2020 (Buttons on a form)
• Exam 1, 2021, Question 6 (XYZ)

9.5 Pros and cons

Advantages

• Open/Closed Principle. You can introduce new subscriber classes without having to change
the publisher’s code (and vice-versa if there is a publisher interface).
• You can establish relations between objects at runtime.

Disadvantages

• Subscribers are notified in random order.

9.6 When to 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.

Copyright: PJ Blignaut, 2022


49

9.7 Further reading

• Banas, D. 2012. https://www.youtube.com/watch?v=wiQdrH2YpT4


• Cardoso, p 245
• Cooper, Chapter 27
• Freeman, Chapter 2, p 46
• Gamma, Helm, Johnson, Vlissides (1995, p 378)
• Martin (2000, p29)
• Martin & Martin (2006, Chapter 32, p 597)
• Nesteruk (2019, p 258)
• Okhravi, C. 2017. Observer pattern.
https://www.youtube.com/watch?v=_BpmfnqjgzQ
• Shvets, p 337

10. State pattern

10.1 Introduction

The state of an object refers to its appearance at a specific moment in time and is represented
by the values of its data fields at that time. The state of an object may change over time. The
State pattern is applicable when there are certain rules about the state or sequence of possible
states. It can also be used when an object must behave differently depending on its current state.

The context (object) does not always know the successor of every given state. The individual
states know what follows them and informs the context.

Example applications

• An elevator serving Floors 0-10, cannot move up beyond Floor 10 and it can never skip a
floor.
• A traffic light can only change colours in the sequence Red, Green, Yellow, Red, …. The
period of a certain state depends on the state, for example the duration of Yellow is much
shorter than that of Green or Red.
• The credit level of a Gold bank account is higher than that of a Silver account.
• A database connection can be open or closed. You cannot open an open connection and you
cannot close a closed connection. No queries against the database are possible if the
connection is closed.
• An academic paper can be in draft format, submitted, accepted, or published. A paper cannot
be accepted before it has been submitted, and it cannot be published before it is accepted.
• The buttons and switches on your smartphone behave differently depending on the current
state of the device:
- When the phone is locked, pressing any button leads to the unlock screen.
- When the phone is unlocked, pressing buttons leads to executing various functions.
- When the battery is low, pressing any button shows the charging screen.

10.2 Conceptual class diagram

The Context class refers to the object with varying state. There are different classes for every
state and the state property of the Context is assigned an instance of one of these classes. An
abstract class represents the states of the object. This class declares an interface that is common

Copyright: PJ Blignaut, 2022


50

to all classes that represent different operational states. Subclasses implement state-specific
behaviour. See the Sta01 (conceptual) example.

Figure 16. State pattern - conceptual

1. The Context current state is indicated by a reference to one of the concrete state objects.

The context knows there is a next state, but it does not always know what it is and asks the
current state what its successor is (context.GetNextState). The current state then informs
the context (state.NextState) through a parameter in a call to context.SetState. Now,
the context can change its state to the correct successor of the current state.

Sometimes, context.GetNextState is not present. The client may call state.NextState


which will then in turn call context.SetState.

2. The State interface or abstract class declares the state-specific methods. State objects may
store a back-reference to the context object. Through this reference, the state can fetch any
required information from the context object, as well as initiate state transitions (inside
NextState). In other words, the current state object requests the context to replace itself
with another.

3. Concrete States provide their own implementations for the state-specific methods. Both
context and concrete states can set the next state of the context and perform the actual state
transition by replacing the state object that is linked to the context.

10.3 Example

Consider the example of the traffic light. It can only change colours in the sequence Red, Green,
Yellow, Red, …. The interval between transitions depends on the current state, for example the
duration of Yellow is much shorter than that of Green or Red.

Copyright: PJ Blignaut, 2022


51

Context

• The initial state must be set in the constructor of the context.


• The context must have a method that will enable the concrete states to change the context's
state (ChangeColour).
• The NextColour method will be called periodically from the client.

class TrafficLight //Context


{
//State
private AColour currentColour;

//Property
public Color colour => currentColour.colour;

//Constructor
public TrafficLight()
{
currentColour = new Red(this);
}

//Called from client


public void NextColour() //GetNextState
{
currentColour.Change();
}

//Called from state


public void ChangeColour(AColour newColour) //SetState
{
currentColour = newColour;
}
} //class TrafficLight

Abstract state class

• Every state instance needs to know what the context is.


• There must be a method where the state of the context can be changed to something else
(Change). In other words, the current state object replaces itself with another as property of
the context.

abstract class AColour


{
protected int delay;

//State property
public Color colour;

//Context
protected TrafficLight t;

//Constructor
public AColour(TrafficLight trafficLight)
{
this.t = trafficLight; //Set context
}

//Abstract method to change state


public abstract void Change();

} //class AColour

Copyright: PJ Blignaut, 2022


52

Concrete states

• The context of every instance is set in the abstract class. So, the concrete states send the
context through to the base class constructor.
• The state-specific properties and behaviour are set per state. In this case, the delay is set
specific to the colour.

class Green : AColour


{
public Green(TrafficLight t): base(t)
{
colour = Color.Green;
delay = 2000;
}

public override void Change()


{
Thread.Sleep(delay);
t.ChangeColour(new Yellow(t));
}
} //class Green

class Yellow : AColour


{
...
} //class Yellow

class Red : AColour


{
...
} //class Red

Client

• For this example, the client instantiates the context and starts the infinite cycle of colour
changes. (We assume no load shedding!!)
class Client
{
static void Main(string[] args)
{
//Instantiate context
TrafficLight trafficLight = new TrafficLight();
Console.WriteLine("Colour is now " + trafficLight.colour.Name);

//Infinite loop
while (true)
{
trafficLight.NextColour();
Console.WriteLine("Colour is now " + trafficLight.colour.Name);
}

} //Main
}

Copyright: PJ Blignaut, 2022


53

Output

The output is generated from the client after every call to the context’s NextColour method.

Class diagram

• I added some extra dotted arrows to indicate the sequence of method calls. For the sake of
clarity, I entered every method in a separate box. This is non-standard UML.

Figure 17. State pattern as applied on a traffic light

10.4 Other examples

• https://www.dofactory.com/net/state-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/state-design-pattern-c-sharp
• https://refactoring.guru/design-patterns/state
• Project 9, 2021 (Elevator)
• Project 9, 2022 (Golf course)
• Project 9, 2023 (Production line)

Copyright: PJ Blignaut, 2022


54

10.5 Pros and cons

Advantages

• Single Responsibility Principle. Organize the code related to particular states into separate
classes.
• Open/Closed Principle. Introduce new states without changing existing state classes or the
context.
• Simplify the code of the context by eliminating bulky state machine conditionals.

Disadvantages

• Applying the pattern can be overkill if a state machine has only a few states or rarely changes.

10.6 When to 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. The pattern suggests that you extract all state-specific code into a set of distinct
classes. As a result, you can add new states or change existing ones independently of each
other, reducing the maintenance cost.

• Use the pattern when you have a class polluted with massive conditionals (if and while
statements) 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. The State pattern lets you compose hierarchies of state
classes and reduce duplication by extracting common code into abstract base classes.

10.7 Further reading

• Cooper, Chapter 28, p 467


• Freeman, et al., p 385
• Gamma, Helm, Johnson, Vlissides (1995, p 392)
• Martin & Martin (2006, Chapter 36, p 729)
• Nesteruk (2019, p 278)
• Okhravi (turnstile gate):
https://www.youtube.com/watch?v=N12L5D78MAA&list=PLrhzvIcii6GNjpARdnO4ueT
UAVR9eMBpc&index=17

Copyright: PJ Blignaut, 2022


55

11. Strategy pattern

11.1 Introduction

Design patterns are about limiting the need for change (Okhravi, 2017). If something
somewhere changes, it should not enforce a series of cascading changes. The principle of
decoupling things makes that possible.

The Strategy pattern is one of the easiest patterns to understand. If something can be done in
many ways, the strategy pattern extracts all of these algorithms into separate classes called
strategies. The original class, called context (or driver), must have a field for storing a reference
to one of the strategies. The context delegates the work to a linked strategy object instead of
executing it on its own.

The Strategy pattern consists of the specification of a common interface that needs to be
implemented by all the classes that belong to the same family or have similar purposes. The
interface exposes a single method for triggering the algorithm encapsulated within the selected
strategy. This way, the context becomes independent of concrete strategies, so you can add new
algorithms or modify existing ones without changing the code of the context or other strategies.

The context is not responsible for selecting an appropriate algorithm for the job. Instead, the
client passes the desired strategy to the context. The Strategy pattern allows us to change the
way of doing something at run time. The client is responsible for deciding which concrete class
needs to be instantiated based on parameters, conditions or business or technical requirements.

11.2 Example scenario

Consider the scenario of collections or lists of objects. We can have different types of lists, for
example static lists (using arrays) and .Net lists (using System.Collections.Generic.List<T>).
Refer to Chapter 21 of Semester 1 again. All lists must implement an interface with the typical
list operations such as Add and Remove and be able to return a sorted list of objects. The Sort
method in the diagram below is private and is called by the GetList method.

Figure 18. List interface with two types of lists

Now, suppose the client wants to have a choice of how the list must be sorted, for example
with a bubble sort or insertion sort or selection sort or not sorted.

Copyright: PJ Blignaut, 2022


56

Doing it wrong

The class diagram below shows one possibility of approaching the challenge.

Figure 19. Sorting different types of lists in different ways - incorrect

This is clearly wrong. We have multiple classes doing the exact same thing. If we want to
change something in, for example, the bubble sort algorithm, we will have to do it twice. If we
want to add another type of list, e.g. a dynamic list using linked lists, we will have to add another
four sorting classes. If we want to add another sorting algorithm, e.g. MergeSort, we will have
to add it for all types of lists. The number of classes will grow exponentially with every addition.

A better approach

We can decouple the sorting strategy from the list implementation. We can now add list types
and sort strategies at will with the one having no effect on the other.

Figure 20. Sorting different types of lists in different ways following the Strategy pattern

This approach follows the principle of favouring composition over inheritance. The sort
strategy is now also a member (component) of the list interface, and it shows clearly that
inheritance is not the best way to facilitate code reuse.

Copyright: PJ Blignaut, 2022


57

11.3 Conceptual class diagram

Figure 21. Strategy pattern - conceptual

1. The Context maintains a reference to one of the concrete strategies and communicates with
this object only via the strategy interface. The context calls the execution method on the
linked strategy object each time it needs to run the algorithm. The context doesn’t know what
type of strategy it works with or how the algorithm is executed.

2. The Strategy interface is common to all concrete strategies. It declares a method that is used
by the context to execute a strategy.

3. Concrete Strategies implement different variations of an algorithm.

4. The Client creates a specific strategy object and passes it to the context.

11.4 Example code

Strategy interface

• The interface takes an array (unsorted) parameter and returns an array (sorted). The context
has to abide with this interface.

interface ISortStrategy<T>
{
T[] Sort(T[] list);
}

Concrete strategies

• The NoSort method returns the original array as is.


• The other sort methods use a local temporary array. Else, the original order of the array will
be lost.
• Again, note that T is limited to IComparable to enable usage of CompareTo. Include the
System namespace.
• The details of InsertionSort and SelectionSort is not given here. See the Visual Studio
code.

Copyright: PJ Blignaut, 2022


58

using System;

class NoSort<T> : ISortStrategy<T>


{
public T[] Sort(T[] array)
{
return array; //Do nothing
}
} //NoSort

class BubbleSort<T> : ISortStrategy<T> where T:IComparable


{
public T[] Sort(T[] list)
{
int n = list.Length;

//Local deep copy of the unsorted list


T[] tmpList = new T[n];
for (int i = 0; i < n; i++)
tmpList[i] = list[i];

//Do the sorting


T temp;
for (int i = 1; i < n; i++)
{
for (int j = 0; j < n - i; j++)
{
if (tmpList[j].CompareTo(tmpList[j + 1]) > 0)
{
temp = tmpList[j];
tmpList[j] = tmpList[j + 1];
tmpList[j + 1] = temp;
}
} //for j
} //for i

return tmpList;
} //Sort
} //class BubbleSort

class InsertionSort<T> : ISortStrategy<T> where T : IComparable


{
public T[] Sort(T[] list)
{
...
} //Sort

} //class InsertionSort

class SelectionSort<T> : ISortStrategy<T> where T : IComparable


{
public T[] Sort(T[] list)
{
...
} //Sort
} //class SelectionSort

Context

• For the purposes of the example, we limit the context below to a .Net list implementation.
An array implementation is available in the Visual Studio code example.
• We use a generic interface so that the list can be used for various data types.

Copyright: PJ Blignaut, 2022


59

• The interface is very limited for the purposes of this discussion. Of course, it should also
contain members such as Contains, RemoveAt, an indexer, an enumerator, etc.
• The class is named MyList to distinguish from System.Collections.Generic.List
which is the private underlying data store type.
• It is important that MyList limits T to IComparable. Else, CompareTo will not be available
to do the sorting. Include the System namespace.
• Note that the sortStrategy reference is private, but there is a public interface method
SetSortStrategy.
• The Sort method is also private and used in GetList.
• The Sort method has to abide with the IStrategy interface. Therefore, the list of items is
cast to an array and the return value is cast back to List<T>.

using System;
using System.Collections.Generic;

interface IList<T>
{
void Add(T item);
void Remove(T item);
T[] GetList();
void SetSortStrategy(ISortStrategy<T> sortStrategy);
} //IList

class MyList<T> : IList<T> where T: IComparable


{
private List<T> items = new List<T>();
private ISortStrategy<T> sortStrategy;

public void SetSortStrategy(ISortStrategy<T> sortStrategy)


{
this.sortStrategy = sortStrategy;
} //SetSortStrategy

private List<T> Sort()


{
if (sortStrategy != null)
{
T[] items_ = items.ToArray(); //Cast List<T> to T[]
items_ = sortStrategy.Sort(items_); //Sort the array
return new List<T>(items_); //Cast T[] to List<T> and return
}
else
return items;
} //Sort

public void Add(T item) { items.Add(item); }


public void Remove(T item) { items.Remove(item); }
public T[] GetList() { return Sort().ToArray(); }
} //class MyList

Copyright: PJ Blignaut, 2022


60

Client

• Note that the end user can determine the sort strategy during runtime.

class Client
{
static void Main(string[] args)
{
//Original list of names
IList<string> lstNames = new MyList<string>();
lstNames.Add("Samual"); lstNames.Add("Jimmy"); lstNames.Add("Sandra");
lstNames.Add("Vivek"); lstNames.Add("Anna"); lstNames.Add("Chris");

//Display original list


Console.WriteLine("\tUnordered list");
Console.WriteLine("\t" + string.Join(", ", lstNames.GetList() ));
Console.WriteLine();

//Loop
char strategy;
do
{
//- Select sort strategy
Console.Write("\tSelect sort strategy: [B], [I], [S], [U], [X]: ");
strategy = Console.ReadKey().KeyChar.ToString().ToUpper()[0];
Console.WriteLine();

//- Instantiate sort strategy


ISortStrategy<string> sortStrategy = new NoSort<string>();
switch (strategy)
{
case 'B': sortStrategy = new BubbleSort<string>(); break;
case 'I': sortStrategy = new InsertionSort<string>(); break;
case 'S': sortStrategy = new SelectionSort<string>(); break;
default: break;
} //switch

//- Set sort strategy


lstNames.SetSortStrategy(sortStrategy);
//- Show list
// GetList will implicitly do the sorting according to the selected strategy
Console.WriteLine("\t" + string.Join(", ", lstNames.GetList()));
Console.WriteLine();
} while (strategy != 'X');

} //Main
} //class Client

Copyright: PJ Blignaut, 2022


61

Output

11.5 Other examples

• https://code-maze.com/strategy/
• https://www.c-sharpcorner.com/UploadFile/dacca2/design-pattern-for-beginner-part-9-
strategy-design-pattern/
• https://www.dofactory.com/net/strategy-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/strategy-design-pattern-c-sharp
• https://refactoring.guru/design-patterns/strategy
• Test 2, 2021, Question 5
• Project 9, 2022 (File strategy)

11.6 Relations with other patterns

• Bridge, State, Strategy (and to some degree Adapter) have very similar structures. All these
patterns are based on composition, which is delegating work to other objects. However, they
all solve different problems.

• State and Strategy:


- Both patterns are based on composition – they change the behaviour of the context by
delegating some work to helper objects.
- Their structures look similar, but they have different intent.
- With Strategy, the client can choose which of several strategies to apply and that only one
strategy at a time is likely to be instantiated and active within the Context class. With the
State pattern, it is possible that all of the different states will be active at once and
switching may occur frequently between them.
- Strategy encapsulates several algorithms that do more or less the same thing, whereas
State encapsulates related classes that each do something somewhat differently.
- Strategy makes these objects completely independent and unaware of each other.
However, State does not restrict dependencies between concrete states and allow them to
alter the state of the context.
- The concept of transition between different States is completely missing in the Strategy
pattern.

Copyright: PJ Blignaut, 2022


62

• Command and Strategy may look similar because you can use both to parameterize an object
with some action. However, they have very different intents.
- You can use Command to convert any operation into an object. The operation’s
parameters become fields of that object.
- Strategy usually describes different ways of doing the same thing.

• Decorator lets you change the skin (interface) of an object, while Strategy lets you change
the guts (details inside).

• Template Method is based on inheritance: it lets you alter parts of an algorithm by extending
those parts in subclasses. Strategy is based on composition: you can alter parts of the object’s
behaviour by supplying it with different strategies that correspond to that behaviour.
Template Method works at the class level, so it is static. Strategy works on the object level,
letting you switch behaviours at runtime.

11.7 Pros and cons

Advantages

• You can swap algorithms used inside an object at runtime.


• You can isolate the implementation details of an algorithm from the code that uses it.
• You can replace inheritance with composition.
• Open/Closed Principle. You can introduce new strategies without having to change the
context.

Disadvantages

• If you only have a couple of algorithms and they rarely change, there is no real reason to
overcomplicate the program with new classes and interfaces that come along with the
pattern.
• Clients must be aware of the differences between strategies to be able to select a proper one.
• A lot of modern programming languages have functional type support that lets you
implement different versions of an algorithm inside a set of anonymous functions. Then you
could use these functions exactly as you’d have used the strategy objects, but without
bloating your code with extra classes and interfaces.

11.8 When to 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 pattern when you have a lot of similar classes that only differ in the way they execute
some behaviour.
• 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 operator that switches between
different variants of the same algorithm.

Copyright: PJ Blignaut, 2022


63

11.9 Further reading

• Banas: https://www.youtube.com/watch?v=-NCgRD9-C6o
• Cardoso, p 231
• Cooper, Chapter 29, p 489
• Gamma, Helm, Johnson, Vlissides (1995, p 403)
• Martin & Martin (2006), Chapter 22, p 407
• Metz, S. 2015. https://www.youtube.com/watch?v=OMPfEXIlTVE
• Nesteruk (2019, p 291)
• Okhravi, C. 2017. Strategy Pattern.
https://www.youtube.com/watch?v=v9ejT8FO-
7I&list=PLrhzvIcii6GNjpARdnO4ueTUAVR9eMBpc&index=1
• Shvets, 369

12. Template method pattern

12.1 Introduction

An algorithm may be written on a high level or low level. High level shows the basic steps only.
Low level gives more details with the basic steps.

High level Lower level Low level


- Step 1 - Step 1 - Step 1
- Step 2 - Step 1a - Step 1a
- Step 3 - Step 1b - Details
- Step 2 - Step 1b
- Step 2a - Details
- Step 2b - Step 2
- Step2c - Step 2a
- Step 3 - Details
- Step 2b
- Details
- Step2c
- Details
- Step 3
- Details

The Template Method pattern is used to specify the high-level steps in a template method inside
an abstract class. These steps are just method calls to where the details are specified. The details
may be different for different implementations of the abstract class. So, some of the detail
methods can be implemented inside the abstract class as well, but some of them may be
implemented in sub-classes.

In short, Template Method defines the skeleton of an algorithm in an abstract base class but
allows subclasses to override specific steps.

Both Template Method and Strategy work with a family of related algorithms. The algorithms
all have the same basic steps, but the details of their implementations vary. Template Method
uses inheritance to solve the problem, whereas Strategy uses delegation.

Copyright: PJ Blignaut, 2022


64

12.2 Conceptual diagram

Figure 22. Template method pattern - conceptual

1. The Abstract Class declares methods that act as steps of an algorithm, as well as the actual
template method which calls these methods in a specific order. The steps may either be
declared abstract or virtual or private. The details of the abstract steps must be specified in
the concrete classes.

2. Concrete Classes can override the virtual steps and must override the abstract steps. The
concrete classes may not override the template method.

12.3 Example 1: Data access

Consider an MS Access database with two tables, one for categories of products and one for
products.

Figure 23. MS Access database with categories and products

In order to display data from the tables through a Visual Studio application, we have to follow
a few steps:

1. Connect to the database


2. Select the data with an SQL query
3. Get the data into a format that can be used by a client
4. Close the connection

If we have to get data from different tables, we will have to repeat the above steps.

Refer to Chapter 14 of Be Sharp with C# for connecting to an MS Access database.

Copyright: PJ Blignaut, 2022


65

Abstract class

• The abstract class contains the common code as well as abstract method declarations for
code that will be different for different tables.
• The template method, Run:
- contains the steps that is necessary to manage the database connection and return the data;
- is the only public method in the class;
- is used by a client to return the data.

using System.Data;

abstract class ADataAccessObject


{
protected string connectionString;
protected DataSet dataSet;

private void Connect()


{
connectionString = "provider=Microsoft.JET.OLEDB.4.0;data source=db1.mdb";
} //Connect

protected abstract void Select();


protected abstract void Display();

private void Disconnect()


{
connectionString = "";
} //Disconnect

// The 'Template Method'


public void Run()
{
Connect();
Select();
Display();
Disconnect();
} //Run
} //class ADataAccessObject

Concrete classes

• The concrete classes provide the specific implementations of the Select and GetData
methods as specified in the abstract class.
• The Select method uses a specific SQL query to fill a data set with data from the database.
• The GetData method steps through the rows in the data and populates a string array with
records, and then returns the array.
• Note that we do not do formatting of output in service classes.

using System.Data;
using System.Data.OleDb;

class Categories : ADataAccessObject


{
protected override void Select()
{
string sql = "SELECT CategoryName FROM Categories";
OleDbDataAdapter dataAdapter
= new OleDbDataAdapter(sql, connectionString);
dataSet = new DataSet();
dataAdapter.Fill(dataSet, "Categories");

Copyright: PJ Blignaut, 2022


66

} //Select

protected override string[] GetData()


{
DataTable dataTable = dataSet.Tables["Categories"];
string[] data = new string[dataTable.Rows.Count];
for (int r = 0; r < dataTable.Rows.Count; r++)
data[r] = dataTable.Rows[r]["CategoryName"].ToString();
return data;
} //GetData
} //class Categories

class Products : ADataAccessObject


{
protected override void Select()
{
string sql = "SELECT ProductName FROM Products";
OleDbDataAdapter dataAdapter
= new OleDbDataAdapter(sql, connectionString);
dataSet = new DataSet();
dataAdapter.Fill(dataSet, "Products");
} //Select

protected override string[] GetData()


{
DataTable dataTable = dataSet.Tables["Products"];
string[] data = new string[dataTable.Rows.Count];
for (int r = 0; r < dataTable.Rows.Count; r++)
data[r] = dataTable.Rows[r]["ProductName"].ToString();
return data;
} //Process
} //class Products

Client

• The client instantiates instances of the concrete classes but refers to them as an instance of
the abstract class. Therefore, the client can call the template method of the abstract class for
both the Category and Product classes.

class Client
{
static void Main(string[] args)
{
//List categories
Console.WriteLine("\tCategories");
ADataAccessObject daoCategories = new Categories();
foreach (string record in daoCategories.Run())
Console.WriteLine("\t" + record);
Console.WriteLine();

//List products
Console.WriteLine("\tProducts");
ADataAccessObject daoProducts = new Products();
foreach (string record in daoProducts.Run())
Console.WriteLine("\t" + record);
Console.WriteLine();

//Wait for user


Console.Write("\tPress any key to exit ... ");
Console.ReadKey();
} //Main
} //class Client

Copyright: PJ Blignaut, 2022


67

Class diagram

Figure 24. Data access application with Template Method pattern

12.4 Example 2: Combination of the Strategy pattern and the Template Method pattern

Consider the example for the Strategy pattern again. You would have noticed that there was
some repetition in the concrete strategies. All but the NoSort strategy had to make a deep copy
of the original list before it could do the sorting. Both the BubbleSort and SelectionSort
algorithms had a fragment of code where two values had to be swapped in memory space.

We can insert an abstract class into the hierarchy that inherits from the ISortStrategy
interface.

Figure 25. Adapted class diagram for the Sorting example


(combination of Strategy and Template Method pattern)

Copyright: PJ Blignaut, 2022


68

Abstract class

• We consider the Sort method in the ISortStrategy interface as the template method. This
method contains the steps for the sorting.
1. Make a deep copy of the incoming array.
2. Do the actual sorting.
• The actual sorting is different for the various sort strategies, so the DoSort method is
abstract and must be overridden in the subclasses for the specific sorting strategy
• The only public member is the template method.
• The Swap method is a helper method and will be called from the subclasses when needed.

abstract class ASorting<T> : ISortStrategy<T>


{
//Template method
public T[] Sort(T[] list)
{
T[] tmpList = DeepCopy(list);
DoSort(tmpList);
return tmpList;
} //Sort

protected abstract T[] DoSort(T[] list);

private T[] DeepCopy(T[] original)


{
int n = original.Length;
T[] tmpList = new T[n];
for (int i = 0; i < n; i++)
tmpList[i] = original[i];
return tmpList;
} //DeepCopy

//Helper method
protected void Swap(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
} //Swap
} //class ASorting

Concrete classes

• Compare the classes below with what we had for the Strategy pattern. The repetition for the
deep copy is gone and the details for the Swap method is done in the abstract class. So, all
that we have in the Sort classes are the differences between the sorting algorithms.

Copyright: PJ Blignaut, 2022


69

class BubbleSort<T> : ASorting<T> where T:IComparable


{
protected override T[] DoSort(T[] list)
{
int n = list.Length;

//Do the sorting


for (int i = 1; i < n; i++)
{
for (int j = 0; j < n - i; j++)
{
if (list[j].CompareTo(list[j + 1]) > 0)
Swap(ref list[j], ref list[j + 1]);
} //for j
} //for i

return list;
} //Sort
} //class BubbleSort

class InsertionSort<T> : ASorting<T> where T : IComparable


{
protected override T[] DoSort(T[] list)
{
...
} //Sort
} //class InsertionSort

class SelectionSort<T> : ASorting<T> where T : IComparable


{
protected override T[] DoSort(T[] list)
{
...
} //Sort
} //class SelectionSort

Client and context

We are combining the Strategy and the Template method pattern. The context class in the
Strategy pattern serves as the client of the Template method pattern. The Sort method in the
Interface strategy is the template method and is called from the context.

12.5 Other examples

• https://www.dofactory.com/net/template-method-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/template-method-design-pattern-c-sharp
• https://refactoring.guru/design-patterns/template-method

12.6 Pros and cons

Advantages

• You can let clients override only certain parts of a large algorithm, making them less affected
by changes that happen to other parts of the algorithm.
• You can pull the duplicate code into a superclass.

Copyright: PJ Blignaut, 2022


70

Disadvantages

• Some clients may be limited by the provided skeleton of an algorithm.


• You might violate the Liskov Substitution Principle by suppressing a default step
implementation via a subclass.
• Template methods tend to be harder to maintain the more steps they have.

12.7 When to 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.

12.8 Further reading

• Cooper, Chapter 30, p 501


• Freeman et al., Chapter 8, p 275
• Gamma, Helm, Johnson, Vlissides (1995, p 415)
• Martin & Martin (2006), Chapter 22
• Nesteruk (2019, p 299)
• Shvets, p 382
• Okhravi:
https://www.youtube.com/watch?v=7ocpwK9uesw&list=PLrhzvIcii6GNjpARdnO4ueTU
AVR9eMBpc&index=13

13. Visitor pattern

13.1 Introduction

The Visitor pattern allows separation of algorithms from the objects on which they operate. So, the
algorithm is external to the class and operates on it from the outside to change the object's appearance
and possibly its behaviour.

As an analogy, you can think of the Visitor as a doctor who gives you an injection against pain. The
doctor is the visitor, you are the object, the injection is the operation, the medicine is the parameter
and the decreased pain represents a change in appearance. The doctor can visit more than one patient
(object) with the same operation. You, the object, can decide to accept the visitor or not.

The Visitor pattern suggests that you place new behaviour into a separate Visitor class instead of
integrating it into an existing class.

When you need to add a new method to a class or hierarchy of classes, you will potentially break the
existing code. With the Visitor pattern, the new method is added as an extra class without touching
any of the existing service code. All that the client does is to accept the new visitor on a specific
instance of the Element class. It can accept the same visitor for different elements (unlike the
command class where the command is coupled with a specific receiver).

Copyright: PJ Blignaut, 2022


71

13.2 Conceptual diagram

Figure 26. Visitor pattern - conceptual

1. The IVisitor interface declares visiting methods that can take concrete elements of an
object structure as arguments. Several overloaded versions of the Visit method may be
declared – each one aimed at another Element subclass.

2. Each Concrete Visitor should implement all Visit methods in the IVisitor interface. Any
data that the visitor needs can be passed to it through a parameter of the constructor.

3. The IElement interface declares a method for accepting visitors. This method should have
one parameter declared with the type of the visitor interface.

4. Each Concrete Element must implement the acceptance method. The purpose of this method
is to redirect the call to the proper visitor’s method.

5. The Client has references to IElement and IVisitor objects. It facilitates external
operations on elements by accepting visitors.

13.3 Example

Consider the example of a document that can be edited, saved to or read from file. In terms of
the Visitor pattern, the document is the element with Edit, Save and Read being visitors. You
can surely argue that there are easier ways to do this but remember that the Visitor pattern
separates the object from the operations. This means that we can now easily add operations
without changing the Document class or any of the existing operations – so we comply with
OCP.

Element interface

• The Element interface declares a single Accept method which takes the visitor as parameter.

interface IDocument
{
void Accept(IVisitor visitor);
}

Copyright: PJ Blignaut, 2022


72

Element

• The Document class declares the attributes of the element and implements the IDocument
interface with the Accept method.
• Note that the set accessor is public in order to allow the visitors to access it from outside.
This is a disadvantage of the Visitor pattern.

class Document : IDocument


{
public string text {get; set;}
public void Accept(IVisitor visitor) { visitor.Visit(this); }
} //class Document

Visitor interface

• The IVisitor interface declares a single Visit method which takes the element as
parameter.

interface IVisitor
{
void Visit(Document doc); //, params object[] values);
} //IVisitor

Concrete visitors

• The concrete visitors include all operations that must be done on the document. All concrete
visitors must implement the IVisitor interface with the details of the specific operation.

class PrintVisitor : IVisitor


{
public void Visit(Document doc)
{
string[] lines = doc.text.Split('\n');
foreach (string line in lines)
System.Console.WriteLine("\t" + line);
} //Visit
} //class PrintVisitor

class ClearVisitor : IVisitor


{
public void Visit(Document doc) { doc.text = ""; }
} //class ClearVisitor

• Any data that the visitor needs can be passed to it through a parameter of the constructor.
class EditVisitor : IVisitor
{
private string text = "";
public EditVisitor(string text) { this.text = text; }
public void Visit(Document doc) { doc.text += text; }
} //class EditVisitor

class ReadVisitor : IVisitor


{
//Read document text from file – see the VS code for details
...
} //ReadVisitor

Copyright: PJ Blignaut, 2022


73

class SaveVisitor : IVisitor


{
//Save document text to file – see the VS code for details
...
} //class EditVisitor

Client

class Client
{
static void Main(string[] args)
{
//Element that can be visited
IDocument doc = new Document();

//Visitors
//- Edit document
doc.Accept(new EditVisitor("\nThis is example text."));
doc.Accept(new EditVisitor("\nSecond line of text."));

//- Print document


doc.Accept(new PrintVisitor());

//- Save document


doc.Accept(new SaveVisitor("Example.txt"));

//Wait
Console.Write("\n\tPress any key to exit ...");
Console.ReadKey();
} //Main
} //class Client

• Check that the file Example.txt is created with the correct content.

Class diagram

Figure 27. Document editing with the Visitor pattern

Copyright: PJ Blignaut, 2022


74

13.4 Other examples

• https://www.dofactory.com/net/visitor-design-pattern
• https://www.dotnettricks.com/learn/designpatterns/visitor-design-pattern-c-sharp
• https://refactoring.guru/design-patterns/visitor
• Project 10, 2021
• Test 3, 2021, Question 5
• Project 10, 2022
• Test 2, 2022, Question 7
• Project 10, 2023 (Queues)

13.5 Pros and cons

Advantages
• Open/Closed Principle. You can introduce a new behaviour that can work with objects of
different classes without having to change these classes.
• Single Responsibility Principle. You can move multiple versions of the same behaviour into
the same class.
• A visitor object can accumulate some useful information while working with various objects.
This might be handy when you want to traverse some complex object structure, such as an
object tree, and apply the visitor to each object of this structure.

Disadvantages
• You need to update all visitors each time a class gets added to or removed from the element
hierarchy.
• Visitors need access to data fields in element classes and therefore these data fields have to
be declared as public.
• Visitors might lack the necessary access to the private fields and methods of the elements
that they are supposed to work with.

13.6 When to use

• Use the Visitor when you need to perform an operation on all elements of a complex object
structure (for example, an object tree). The Visitor pattern lets you execute an operation over
a set of objects with different classes by having a visitor object implement several variants
of the same operation, which correspond to all target classes. See example of calculating
area of different types of shapes.
• Use the Visitor to clean up the business logic of auxiliary behaviours. The pattern lets you
make the primary classes of your app more focused on their main jobs by extracting all other
behaviours into a set of visitor classes.
• Use the pattern when a behaviour makes sense only in some classes of a class hierarchy, but
not in others. You can extract this behaviour into a separate visitor class and implement only
those visiting methods that accept objects of relevant classes, leaving the rest empty.

13.7 Further reading

• Cooper, Chapter 31, p513


• Gamma, Helm, Johnson, Vlissides (1995, p 423)
• Martin & Martin (2006, Chapter 35, p 685)
• Nesteruk (2019, p 305)

Copyright: PJ Blignaut, 2022


75

13.8 Comparison with similar patterns

14. Chapter summary

• Chain of Responsibility allows passing of 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.
• The Command pattern turns a request into a stand-alone object that contains all information
about the request. This transformation allows parameterization of methods.
• The Iterator traverses elements of a collection without exposing its underlying representation.
• The Mediator reduces chaotic dependencies between objects. The pattern restricts direct
communications between the objects and forces them to collaborate only via a mediator
object.
• The Memento saves and restores the previous state of an object without revealing the details
of its implementation.
• The Null Object pattern uses an object which implements the expected interface, but whose
method bodies are empty.
• The Observer facilitates a subscription mechanism to notify multiple objects about any events
that happen to the object that they are observing.
• The State pattern allows an object to alter its behaviour when its internal state changes. It
appears as though the object changed its class.
• The Strategy pattern puts each one of a family of algorithms into a separate class and make
their objects interchangeable.
• The Template method pattern defines the skeleton of an algorithm in the superclass but let
subclasses override specific steps of the algorithm without changing its structure.
• The Visitor pattern separates algorithms from the objects on which they operate.
Compare:
- Visitor: Every visitor does something differently.
Overloaded Visit methods are possible with different types of elements.
Client: element.Accept(visitor)
- Strategy: Every class does the same thing but in a different way.

Copyright: PJ Blignaut, 2022


76

- Command: A command object has a reference to the object on which it operates.


Commands and receivers are coupled. An invoker initiates requests.
Client: invoker.ExecuteCommand(new Command(receiver))
- https://www.quora.com/What-is-the-difference-between-a-visitor-and-a-command-
design-pattern-I-cant-see-the-difference

15. References and Bibliography

• Cardoso, A. 2019. Implementing Design Patterns in C# and .NET 5: Build Scalable, Fast,
and Reliable .NET Applications Using the Most Common Design Patterns. English Edition.
BPB Publications. Kindle Edition.
• Cooper, J.W. 2002. C# Design Patterns: A Tutorial, Addison Wesley
• Freeman, E., Freeman, E., Bates, B., Sierra, K. 2004. Head First Design Patterns. O'Reilly.
• Gamma, E., Helm, R., Johnson, R., Vlissides, J. 1994. Design Patterns: Elements of
Reusable Object-Oriented Software. Addison- Wesley.
• Martin, R.C. 2000. Design Principles and Patterns.
https://fi.ort.edu.uy/innovaportal/file/2032/1/design_principles.pdf
• Martin, R.C., Martin, M. 2006. Agile Principles, Patterns, and Practices in C#. Prentice Hall.
• Nesteruk, D. 2019. Design Patterns in .NET. Apress. Kindle Edition.
• Ohkravi, C. 2017. Design patterns.
https://www.youtube.com/playlist?list=PLrhzvIcii6GNjpARdnO4ueTUAVR9eMBpc
• Shvets, A. 2020. Dive into Design Patterns. https://refactoring.guru/design-patterns/book.

Copyright: PJ Blignaut, 2022


77

16. Quick reference: Class diagrams of creational design patterns

Chain of responsibility Command

Copyright: PJ Blignaut, 2022


78

Iterator Mediator

Memento Null Object

Copyright: PJ Blignaut, 2022


79

Observer State

Strategy Template method

Copyright: PJ Blignaut, 2022


80

Visitor

Copyright: PJ Blignaut, 2022

You might also like