Chapter 5
Behavioral Design Patterns – Part II
Introduction
Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. Behavioral patterns describe patterns of objects or classes and communication patterns between them. These patterns characterize complex control flow that is difficult to follow at run-time. They shift your focus away from the control flow to let you concentrate on how objects are interconnected.
Structure
In this chapter, we will cover the following topics:
Behavioral design patterns
Interpreter
Iterator
Memento
Observer
Visitor
Objectives
In Chapter 4, Behavioral Design Patterns: Part I, you got acquainted with the most famous behavioral design patterns. In this chapter, you will get to know the rest of the design patterns of this category. These design patterns may be less famous, but in terms of importance and usage, they are very important and widely used and can easily solve complex problems.
Behavioral design patterns
In this category of design patterns, there is a total of eleven different design patterns. In this chapter, the rest of the design patterns of this category are introduced, which are:
Interpreter: Tries to interpret the given language with the help of defined rules.
Iterator: Tries to provide the possibility of navigation and sequential access to the collection members.
Memento: Tries to capture and save an object's state by keeping the encapsulation rules.
Observer: When an event occurs, it can notify other objects.
Visitor: It tries to perform various operations on an object without changing its structure.
Interpreter
Name:
Interpreter
Classification:
Behavioral design patterns
Also known as:
---
Intent:
This design pattern tries to interpret the given language with the help of defined rules.
Motivation, Structure, Implementation, and Sample code:
Suppose we want to define rules using SQL language so that we can use them to execute the existing methods inside a class. For example, if we assume the following class:
public class Sample{
public void SomeMehtod(){}
}
We want to execute SomeMethod using SQL language rules. For example:
Select SomeMethod From Sample
If we consider the rules of the SQL language very limited and assume that the commands used are only SELECT and FROM commands, then we can say that the expressions raised in this language will be in the following form:
In general, we can consider the following tree for the preceding language structure:
Figure%205.1.png
Figure 5.1: Simplified SQL language tree
When implementing the infrastructure to interpret these types of expressions, we usually need to define a class for each non-Terminal and implement how to interpret each one. In other words, the leaves in the preceding tree are Terminals, and the nodes are non-terminals. Therefore, we have three non-terminals named Select, From, and Query, and we only have one terminal to interpret the leaves. Also, exchanging some common information between terminals and non-terminals may be necessary when interpreting this phrase. In this situation, we will need a separate class to send information between Terminals and n-on-terminals. This class is also called Context.
With these explanations, the following class diagram can be considered:
Figure%205.2.png
Figure 5.2: Interpreter design pattern UML diagram
As seen in the Figure 5.2 class diagram, a class called LiteralExpression is defined, which is responsible for interpreting Terminals. Also, as shown, separate classes are defined for each non-terminal. All these four classes (LiteralExpression, QueryExpression, FromExpression, and SelectExpression) have also implemented the ISqlExpression interface. Therefore, they must provide their implementation for the Interpret method, and through that, they implement how to interpret each expression. Through the SqlContext class, we also define common information between these four classes. That is, by setting the Namespace value and providing the expression. The client determines that this expression must be interpreted and executed in the field of the provided Namespace.
For the Figure 5.2 class diagram, we have the following code:
public class SqlContext
{
public string Namespace { get; set; }
}
public interface ISqlExpression
{
string Interpret(SqlContext context);
}
public class QueryExpression : ISqlExpression
{
private readonly ISqlExpression selectExpression;
private readonly ISqlExpression fromExpression;
public QueryExpression(
ISqlExpression selectExpression, ISqlExpression fromExpression)
{
this.selectExpression = selectExpression;
this.fromExpression = fromExpression;
}
public string Interpret(SqlContext context)
{
var className = fromExpression.Interpret(context);
var methods = selectExpression.Interpret(context);
var targetType = this.GetType()
.Assembly.GetType($"{context.Namespace}.{className}");
var targetObj = Activator.CreateInstance(targetType);
var methodsList = targetType.GetMethods()
.Where(x => methods.Split(',')
.Select(c => c.Trim()).Contains(x.Name));
foreach (var item in methodsList)
item.Invoke(targetObj, null);
return "Expression interpreted!";
}
}
public class SelectExpression : ISqlExpression
{
private readonly ISqlExpression expression;
public SelectExpression(ISqlExpression expression)
=>this.expression = expression;
public string Interpret(SqlContext context)
=> expression.Interpret(context);
}
public class FromExpression : ISqlExpression
{
private readonly ISqlExpression expression;
public FromExpression(ISqlExpression expression)
=> this.expression = expression;
public string Interpret(SqlContext context)
=> expression.Interpret(context);
}
public class LiteralExpression : ISqlExpression
{
private readonly string statement;
public LiteralExpression(string statement) => this.statement = statement;
public string Interpret(SqlContext context) => this.statement;
}
As it is clear from the preceding method, it is assumed in this code that the methods do not have input parameters and output types. We must change and transform the interpretation methods provided in Terminal or NonTerminals to solve this limitation. To use this construct, suppose the user provides the following input:
string query = "Select SomeMethod, OtherMethod From Config";
In the preceding query, the user intends to execute the SomeMethod and OtherMethod methods of the Config class in the Sample namespace. With these interpretations, the following statement can be interpreted and implemented:
SqlContext context = new() { Namespace = "Sample" };
var parts = Regex.Split(query, " From ", RegexOptions.IgnoreCase)
.Select(x => x.Trim()).ToArray();
LiteralExpression methodExpression = new(parts[0][6..]);
LiteralExpression classExpression = new(parts[1]);
SelectExpression selectExpression = new(methodExpression);
FromExpression fromExpression = new(classExpression);
QueryExpression queryExpression = new(selectExpression, fromExpression);
Console.WriteLine(queryExpression.Interpret(context));
Participants:
AbstractExpression: In the preceding scenario, it is ISqlExpression, which is responsible for providing the template for interpretation for all the nodes in the tree.
TerminalExpression: In the preceding scenario, it is the same as LiteralExpression, which is responsible for interpreting Terminals in the provided rules.
NonTerminalExpression: In the preceding scenario, it is QueryExpression, SelectExpression, and FromExpression, responsible for interpreting NonTerminals. In fact, in linguistic rules, a NonTerminal class will be needed for each R in the rule R:=R1 R2 ... Rn in linguistic rules xt: In the preceding scenario, it is the same as SqlContext and maintains information shared between all classes during interpretation.
Client: It is the same user who is responsible for creating or presenting the abstract syntax tree. This tree is the same sentence presented in the form of linguistic rules and is made by combining TerminalExpression and NonTerminalExpression. Another task of the user is to start the interpretation process.
Notes:
It may not be necessary to use Context or NonTerminal in different uses.
The iterator pattern can be used to traverse the structure.
Consequences: Advantages
Because rules are defined through classes, language rules can be changed or expanded using inheritance.
Usually, the classes in the abstract syntax tree have similar implementations. Therefore, it is easy to write new rules with the help of code generators, which can accelerate development.
Different interpretation methods can be easily defined.
Consequences: Disadvantages
Implementation and maintenance of more complex language rules can be difficult.
Applicability:
When we need to give the end user the possibility to design parts of the program at runtime by providing simple syntaxes. For example, to have a calendar in HTML, we can consider the following code:
Now consider that we want to leave the calendar placement on the page and the initial settings to the HR expert. Since this user needs to gain more technical knowledge in the field of HTML, it will be difficult for him to use the preceding code. Therefore, by defining a series of language rules, it is possible to give this feature so that this user can provide the following code:
[form postTo="process"]
[datePicker minDate="some_val" maxDate="some_val" allowTime="false"]
[end]
Then by receiving this expression and interpreting it, we will reach the desired code.
Related patterns:
Some of the following design patterns are not related to the interpreter design pattern, but to implement this design pattern, checking the following design patterns will be useful:
Composite
Iterator
Visitor
Flyweight
Iterator
Name:
Iterator
Classification:
Behavioral design patterns
Also known as:
Cursor
Intent:
This design pattern tries to navigate its members without paying attention to the internal structure of the collections.
Motivation, Structure, Implementation, and Sample code:
We often come across different data structures in the programs we produce. Data structures include trees, graphs, linked lists, and so on. Each of these data structures has different methods for traversing. Even a well-defined and fixed data structure, such as a tree, has different ways of traversing it.
Suppose we are facing a collection and must traverse it based on the stack data structure. Implementing this will be a simple task. But the problem comes when we need to provide the queue method in addition to the stack method. Gradually, however, the client will face complexity. For the client, as a user, it may not be very important what method we use to traverse a collection. What is important for them is that we can cover a series of requirements for him. Requirements such as finding the next or previous member in the collection, finding the current member in the collection or identifying the first or last member in the collection, and so on.
On the other hand, regardless of the traversing method, we face different collections during software production. For example, the list of registered people, the list of cities between the origin and destination, the list of ticket purchase requests, and so on. These collections need a method to traverse to be traversed. For example, to traverse the list of cities between origin and destination, we may use a graph and queue for the list of people registered to buy tickets.
With the preceding scenario, we are faced with the following questions:
What do we want to traverse?
How do we want to traverse?
Actually, by using the Iterator design pattern, we want to answer these two questions. It is important to note that we may have multiple traversing methods for the same collection or use one traversing method to traverse several different collections.
Imagine we are asked to provide an infrastructure to send messages to our connections on social networks. In this requirement, we face various social networks such as LinkedIn, Instagram, and so on. Our communications are also two groups. The first group is close friends, and the second group is colleagues. The way to traverse connections on LinkedIn and Instagram can be completely different. So far in the scenario, we are facing an abstraction called a social network, which introduces browsing the list of friends and colleagues. LinkedIn and Instagram are a type of social network and offer their implementation to navigate friends and colleagues. Also, to be able to have the list of connections, we need to be able to traverse through the profile. The profile browser is itself an abstraction, and the LinkedIn and Instagram browsers use it during the traversing requirements. With these explanations, we are faced with the Figure 5.3 class diagram:
Figure%205.3.png
Figure 5.3: Iterator design pattern UML diagram
As it is clear from Figure 5.3, we are facing two different types of collections called InstagramCollection and LinkedInCollection. Each of these classes is connected to its appropriate iterator using the factory method pattern. For each of these two sets, we have an iterator, and each iterator has implemented the operation necessary to traverse the set:
public class Profile
{
public string Name { get; set; }
public string Email { get; set; }
public short Gender { get; set; }
public override string ToString()
=> $"Name: {Name},
Email: {Email},
Gender: {(Gender == 1 ? "Male" : "Female")}";
}
public abstract class ProfileIterator
{
protected int _currentPosition;
protected Profile[] _profiles;
public virtual Profile First()
{
_currentPosition = 0;
return _profiles[_currentPosition];
}
public virtual Profile Last()
{
_currentPosition = _profiles.Length - 1;
return _profiles[_currentPosition];
}
public virtual Profile Next() => _profiles[_currentPosition++];
public virtual Profile Prev() => _profiles[--_currentPosition];
public virtual bool HasNext() => _currentPosition < _profiles.Length; public virtual bool IsFirst() => _currentPosition == 0;
}
public class LinkedInIterator : ProfileIterator
{
public LinkedInIterator(LinkedInCollection linkedIn, short gender)
=> _profiles = linkedIn.Profiles.Where(x => x.Gender == gender).ToArray();
}
public class InstagramIterator : ProfileIterator
{
public InstagramIterator(InstagramCollection instagram, short gender)
=> _profiles = instagram.Profiles
.Where(x => x.Gender == gender).ToArray();
}
public interface ISocialNetworkCollection
{
IProfileIterator CreateMaleIterator();
IProfileIterator CreateFemaleIterator();
}
public class LinkedInCollection : ISocialNetworkCollection
{
public Profile[] Profiles
=> new Profile[] {
new() { Name="John", Email="john@linkedin.com", Gender = 1},
new() { Name="Paul", Email="paul@linkedin.com", Gender = 1},
new() { Name="Sara", Email="sara@linkedin.com", Gender = 0},
new() { Name="Antonio", Email="anton@linkedin.com", Gender = 1},
new() { Name="Liza", Email="liza@linkedin.com", Gender = 0}
};
public IProfileIterator CreateMaleIterator()
=> new LinkedInIterator(this, 1);
public IProfileIterator CreateFemaleIterator()
=> new LinkedInIterator(this, 0);
}
public class InstagramCollection : ISocialNetworkCollection
{
public Profile[] Profiles
=> new Profile[] {
new() { Name = "Vahid", Email = "john@instagram.com", Gender = 1 },
new() { Name = "Narges", Email = "paul@instagram.com", Gender = 0 },
new() { Name = "Reza", Email = "sara@instagram.com", Gender = 1 },
new() { Name = "Hasan", Email = "anton@instagram.com", Gender = 1 },
new() { Name = "Maryam", Email = "liza@instagram.com", Gender = 0 }
};
public IProfileIterator CreateMaleIterator()
=> new InstagramIterator(this, 1);
public IProfileIterator CreateFemaleIterator()
=> new InstagramIterator(this, 0);
}
As it is clear in the preceding code, each of the iterators can use its own method for traversing and implement the First, Last, Next, Prev, HasNext, and IsFirst methods. Now, to use the preceding structure, we can use the following code:
ISocialNetworkCollection social = new LinkedInCollection();
var profiles = social.CreateMaleIterator();
while (profiles.HasNext())
{
Console.WriteLine(profiles.Next());
}
Console.WriteLine("###############");
while (!profiles.IsFirst())
{
Console.WriteLine(profiles.Prev());
}
In the preceding code, we first specify that we intend to connect with LinkedIn, and then continue to traverse the collection using CreateMaleIterator and HasNext methods, and so on.
Participants:
Iterator: In the preceding scenario, it is ProfileIterator, which is responsible for defining the format for accessing and browsing the collection.
Concrete iterator: In the preceding scenario, it is the InstagramIterator and LinkedInIterator, which is responsible for implementing the format defined by the iterator.
Aggregate: In the preceding scenario, it is the same as ISocialNetwork Collection and is responsible for defining the template for defining iterator.
Concrete aggregate: In the preceding scenario, it is the same as InstagramCollection and LinkedInCollection and is responsible for implementing the template provided by aggregate.
Client: It is the same user who navigates aggregate through the iterator.
Notes:
One of the tasks of the ConcreteIterator is to maintain the position of the current object.
In the implementation of the preceding scenario, instead of the ProfileIteratror abstract class, the interface can also be used.
In order to implement this design pattern in .NET, there are concepts such as enumerator and enumerable. Understanding these two can help in implementing this pattern. The enumerator is actually responsible for finding objects in a collection, which is called enumerable. In order to implement this design pattern with the mentioned concepts, IEnumerable and IEnumerator interfaces can be used:
public class InstagramIterator : IEnumerator
{
int _currentPosition = -1;
Profile[] _profiles;
public InstagramIterator(Profile[] profiles) => _profiles = profiles;
public object Current => _profiles[_currentPosition];
public bool MoveNext() => (++_currentPosition) < _profiles.Length; public void Reset() => _currentPosition = -1;
}
public class InstagramCollection : IEnumerable
{
private readonly short gender;
Profile[] Profiles => new Profile[] {
new() { Name = "Vahid", Email = "john@instagram.com", Gender = 1 },
…
};
public InstagramCollection(short gender) => this.gender = gender;
public IEnumerator GetEnumerator()
=> new InstagramIterator(
Profiles.Where(x => x.Gender == gender).ToArray());
}
We can use the preceding code as follows:
InstagramCollection social = new InstagramCollection(0);
var socialEnum = social.GetEnumerator();
while (socialEnum.MoveNext())
Console.WriteLine(socialEnum.Current);
In the preceding implementation, instead of implementing the IEnumerator interface, we could also use yield. In this case, the code would change as follows:
public class InstagramCollection : IEnumerable
{
private readonly short gender;
Profile[] Profiles => new Profile[] {
new() { Name = "Vahid", Email = "john@instagram.com", Gender = 1 },
…
};
public InstagramCollection(short gender) => this.gender = gender;
public IEnumerator GetEnumerator()
{
yield return
from item in Profiles.Where(x => x.Gender == gender)
select item;
}
}
Pay attention to the use of yield in the preceding code. In fact, when using yield, we specify that we are dealing with an iterator, and there is no need to use the IEnumerator interface anymore. The preceding code can be used as follows:
InstagramCollection social = new InstagramCollection(0);
foreach (var item in social)
Console.WriteLine(item);
Factory method design patterns can be used to build different Iterators.
Consequences: Advantages
Separating collection traversing logic from the main business of code can improve code readability. This advantage is in line with the single responsibility principle.
When we intend to add a new traversing method or a new data structure is added, the infrastructure can be expanded without changing the previous codes. (Open/Close principle)
It is possible to traverse a collection in parallel.
Consequences: Disadvantages
In the whole program, we only face simple collections; using this pattern can increase the complexity.
In the case of a series of special collections, it can be more optimal to refer directly to the elements of the collection instead of using this design pattern.
Applicability:
When you are faced with a collection that has complications in traversing this collection, and you do not intend to convey this complexity to the user.
When you need to traverse a set in different ways, or the implementation method of the sets itself is not clear.
Related Patterns:
Some of the following design patterns are not related to the iterator design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:
Factory method
Composite
Interpreter
Memento
Visitor
Memento
Name:
Memento
Classification:
Behavioral design patterns
Also known as:
Token
Intent:
This design pattern tries to capture and store the state of an object by maintaining the encapsulation rules. This makes it possible to restore the state of the object later. This restoration of the previous state of the object makes it possible to implement mechanisms such as undo and checkpoint mechanisms.
Motivation, Structure, Implementation, and Sample Code:
Suppose a requirement has been raised and a questionnaire has been asked to be designed for users. Users will be allowed to answer the questions of the questionnaire incompletely and save the answers as a draft before sending the final questionnaire. The users can come back later and complete the questionnaire, for which the draft must be uploaded, and the user must answer the rest of the questions or edit the previous answers and finally register the final form of the questionnaire and send the answers.
According to the scenario, it is clear that we need to allow the user to save the desired object before the final registration. Saving of the object should be done in such a way that the encapsulation is not damaged, and the implementation process and the internal information of the object are not leaked. One way to store an object state is to refer to individual properties and fields of the object and store their values in a repository. This way is often not practical because objects usually hide much of their data using different access levels, such as private.
public class Survey { public class Survey_Copy {
public string Prop1{get; set;} public string Prop1_Copy {get; set;}
private string Prop2; ?//It is private and we do not have access to it!
} }
If we assume that the desired object does not hide any details and that all its fields and properties have public access, then the proposed method will not be practical. Because later, if we want to add a new property or field to the class, we will need to change the class that has the task of copying the object, which can cause problems and disturb the maintenance and development of the code.
Even if we assume that we have ignored the preceding problems, we are facing a more serious problem. The problem is that when we need to copy the state of a class in order to be able to store values, properties, and fields, we need a class where these properties and fields have public access in that class so that we can read and write operations. To do this will make all the details of the class that were previously hidden in line with encapsulation fully accessible.
With the conditions stated in the preceding sections, it seems that there are no more than two ways:
Violating encapsulation and making available all the hidden details of the class.
Maintain encapsulation in which state saving is impossible.
There is another way, and that is to do this inside the class instead of trying to get an image of the state of the object and copy it from outside the class. Then we save the prepared image in another object called a memento. The content of this object is not available except through the class that created it. Next, you can save the generated memento in another object called caretaker. In this storage, the stack data structure can be used. Since the caretaker has limited access to the memento, it does not have access to change the state. However, the class that created the memento has full access to the state and can change it and restore the state.
With the preceding explanations, it is clear that in this design, we need an element to be able to take a photo and retrieve it. This element is called a memento. On the other hand, we need a German to be able to save the photo thereafter it is taken, and final implements, a German that plays the role of the target class, and we take pictures from it. According to the preceding explanations, the Figure 5.4 class diagram can be considered:
Figure%205.4.png
Figure 5.4: Memento design pattern UML diagram
As can be seen in the Figure 5.4 class diagram, the Survey class has the same role as a questionnaire. The questionnaire has a series of questions and answers to these questions, which are available through _state (SurveyState class is not included for simplicity). Users can submit the questionnaire in which the questionnaire information will be registered permanently. Also, users can use SaveDraft to save the draft of the questionnaire to return later and complete the questionnaire. The RestoreDraft method is also defined to restore the draft of the questionnaire.
In order to be able to save the questionnaire draft, the Survey class needs to save the current state of the questionnaire by creating a new object from SurveySnapshot. Next, this draft is added to the list of drafts through the AddMemento method in the SurveyCaretaker class. In this case, the user can have several copies of the draft and restore them in order (by doing this, the user will be able to undo it). You can also browse the history of changes through the SurveyCaretaker class.
For the Figure 5.4 class diagram, the following code can be considered:
public class SurveyState
{
public Dictionary
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (var item in Answers)
sb.Append($"Question:{item.Key},
Answer: {item.Value}{Environment.NewLine}");
return sb.ToString();
}
}
public class Survey
{
private SurveyState _state = new SurveyState()
{ Answers = new Dictionary
public void Submit() => Console.WriteLine(_state);
public SurveySnapshot SaveDraft()
=> new(new SurveyState { Answers = new(_state.Answers) });
public void RestoreDraft(SurveySnapshot snapshot)
=> _state = snapshot.Restore();
public void SetAnswer(int question, string answer)
=> _state.Answers.Add(question, answer);
}
public class SurveySnapshot{
private readonly SurveyState _state;
public SurveySnapshot(SurveyState state) => _state = state;
public SurveyState Restore() => _state;
}
public class SurveyCaretaker
{
readonly Stack
public void AddMemento(SurveySnapshot snapshot) => _mementos.Push(snapshot);
public SurveySnapshot GetMemento() => _mementos.Pop();
}
In the preceding code and in the Survey class, when executing the SaveDraft method, it should be noted that the copy of the object sent to SurveySnapshot is of deep type. To execute the preceding code, you can proceed as follows:
1. SurveyCaretaker caretaker = new SurveyCaretaker();
2. Survey s = new Survey();
3. s.SetAnswer(1, "A");
4. s.SetAnswer(2, "B");
5. caretaker.AddMemento(s.SaveDraft());
6. s.SetAnswer(3, "C");
7. caretaker.AddMemento(s.SaveDraft());
8. s.SetAnswer(4, "D");
9. s.RestoreDraft(caretaker.GetMemento());
10. s.Submit();
In the preceding code, new answers are recorded in lines 3 and 4, and drafts are saved in line 5. A new answer is added in line 6, and a new draft is saved again in line 7. In line 8, another answer is recorded, and in line 9, the last draft is retrieved. By doing this, the last answer that was given (line 8) is lost. Therefore, by executing the preceding code, the available answers will be as follows:
1-A
2-B
3-C
Participants:
Memento: In the preceding scenario, it is the same as SurveySnapshot and is responsible for saving the internal state of the originator object. Another task of this class is to determine what information should be stored in the originator.
Originator: In the preceding scenario, it is the Survey that is responsible for preparing the image of the current situation and restoring the situation based on the existing image.
Caretaker: In the preceding scenario, he is the SurveyCartaker and is responsible for maintaining the images. This class should not manipulate the content of the image.
Client: It is the same user who executes the code through the mediator and ConcreteMediator.
Notes:
There are different ways to implement this design pattern. Apart from the preceding method, which is called the nested class method, this design pattern can also be implemented using interfaces. For example, the originator was sent to the memento through the constructor parameter, in which case each memento is connected to its originator. In this way, the memento itself can perform the task of restoring the status, and the implementation of this work can be removed from the caretaker. Another way to implement this design pattern is to use the narrow interface method. In this implementation method, the memento class is defined as an internal class in the originator, and in this way, the memento object is encapsulated in the originator. However, the caretaker communicates with the IMemento interface instead of the memento class. In this way, we have the following code:
public class Survey
{
private class SurveySnapshot : ISurveySnapshot
{
private readonly SurveyState _state;
public SurveySnapshot(SurveyState state) => _state = state;
public SurveyState Restore() => _state;
}
private SurveyState _state = new() { Answers = new() };
public void Submit() => Console.WriteLine(_state);
public ISurveySnapshot SaveDraft()
=> new SurveySnapshot(new SurveyState {
Answers = new(_state.Answers) });
public void RestoreDraft(ISurveySnapshot snapshot)
=> _state = snapshot.Restore();
public void SetAnswer(int question, string answer)
=> _state.Answers.Add(question, answer);
}
public interface ISurveySnapshot
{
SurveyState Restore();
}
public class SurveyCaretaker
{
readonly Stack
public void AddMemento(ISurveySnapshot snapshot)
=> _mementos.Push(snapshot);
public ISurveySnapshot GetMemento() => _mementos.Pop();
}
In this method, the originator has access to all aspects of the memento, while the caretaker and other objects only have access to the information provided in the interface framework (in the preceding example, the ISurveySnapshot interface). This means that the caretaker is explicitly prohibited from making changes to the originator's status.
This design pattern is often associated with the use case of undo in minds, but another use case is in the design of transactions. By using this design pattern in the design of transactions, you can support rollback and commit operations.
This design pattern can be used alongside the command design pattern. In this case, different operations can be performed on the destination object with the help of the command design pattern, and through memento, before executing the command, you can save the status of the object.
Consequences: Advantages
It is possible to save the internal state of the object without violating encapsulation.
Using the caretaker, a part of the originator codes related to the history of changes is removed and transferred to the caretaker, which simplifies and improves code maintenance.
Consequences: Disadvantages
If mementos are produced too much, memory consumption will increase.
In order for the caretaker to remove unused mementos, they must monitor the originator's life cycle.
Applicability:
When there is a need to implement undo and redo mechanisms.
When there is a need to save checkpoints, for example, in the design of computer games, with the help of a checkpoint, we save the state of the object, and later we can restore it.
Related patterns:
Some of the following design patterns are not related to the memento design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:
Prototype
Command
Iterator
Observer
Name:
Observer
Classification:
Behavioral design patterns
Also known as:
Dependents or publish-subscribe
Intent:
This design pattern tries to define a mechanism for 1-N dependencies so that when there is a change in the state of an object, all the dependents of that object are automatically informed of the change.
Motivation, Structure, Implementation, and Sample code:
Suppose you are visiting an online shopping website. There are various products on this website. When you visit this website, you intend to buy shoes, but currently, the shoes you want are not available. On the other hand, you need to be informed as soon as the shoes you want are available so that you can buy them. Also, we need to register a login to the database as soon as the desired shoe is available so that we know on what date and time the shoe was available.
To implement this requirement, a simple way is for the user to visit the website every day and make a purchase when the shoes are available. It is clear that in this method, the user has to visit the website many times, which eventually causes the user to stop buying shoes from that website and buy shoes from another collection. Therefore, over time, the online store will lose its customers and face a decrease in profits. For this reason, it can be said that this method is not a suitable method.
Another way to implement this mechanism is to send a notification (via SMS or email) to all users every time the shoes are available in the store. This method also has its own problem. In this method, a large number of users receive messages that are not of their interest. This causes the sent notification to become spam gradually and has a negative impact on the customer's shopping experience.
Observer design pattern tries to solve this kind of problem. The approach is such that only the intended user can become a member of the list of subscribers of the desired shoe announcement. Therefore, whenever the desired shoe becomes available, a notification will be sent to the desired user. In fact, what happens is very similar to the magazine subscription mechanism, where people buy a subscription to a magazine, and from then on, whenever the magazine is published, a copy of the magazine is sent to its subscribers. In the proposed scenario, the mechanism is the same procedure.
As in the example of the magazine, we are facing publishers and subscribers, and we have these roles in the mentioned scenario. In terminology, the publisher is called the publisher, and the subscriber is called the subscriber. Now, every time the shoes are available, we are faced with two different customers. The first subscriber is the customer, and the second subscriber is the log recording classubscription, ability of shoes is communicated to these two classes, and each class, based on its task, implements and executes the appropriate business.
As subscribers can subscribe to the publication, they should be able to cancel their subscriptions in a similar way. In this case, the subscriber is no longer interested in receiving notifications from publications.
With the preceding explanations, Figure 5.5 class diagram can be considered for the proposed scenario:
Figure%205.5.png
Figure 5.5: Observer design pattern UML diagram
As can be seen in the Figure 5.5 class diagram, there is a publisher named ShowNotifier and two subscribers named CustomerObserver and LoggingObserver. Publishing has three methods: Subscribe to register a subscription, Unsubscribe to delete a subscription, and Notify to send notifications. For Figure 5.5 class diagram, the following codes can be imagined:
public class Shoe
{
public int Size { get; set; }
public string Color { get; set; }
}
public interface IShoeObserver
{
void Update(Shoe param);
}
public class CustomerObserver : IShoeObserver
{
public void Update(Shoe param)
=> Console.WriteLine("Sending email to customer...");
}
public class LoggingObserver : IShoeObserver
{
public void Update(Shoe param) => Console.WriteLine("Saving log...");
}
public interface IShoeNotifier
{
void Subscribe(IShoeObserver param);
void Unsubscribe(IShoeObserver param);
void Notify(Shoe param);
}
public class ShoeNotifier : IShoeNotifier
{
private static List
static ShoeNotifier() => _subscribers = new List
public void Subscribe(IShoeObserver param) => _subscribers.Add(param);
public void Unsubscribe(IShoeObserver param) => _subscribers.Remove(param);
public void Notify(Shoe param)
{
foreach (var item in _subscribers)
item.Update(param);
}
}
As it can be seen in the preceding code, for simplicity, the list of subscribers is kept in a static field called _observers. The reason this field is static is to preserve its value between requests. In order to initialize this field, a static constructor is also used. In more realistic implementations, other repositories may be used to store the list of subscribers. To use the preceding codes, you can proceed as follows:
1. IShoeObserver customerObserver = new CustomerObserver();
2. IShoeObserver loggingObserver = new LoggingObserver();
3. IShoeNotifier notifier = new ShoeNotifier();
4. notifier.Subscribe(customerObserver);
5. notifier.Subscribe(loggingObserver);
6. notifier.Notify(new Shoe { Size = 45, Color = "Red" });
In the preceding code, as soon as line 6 is executed, the codes in the Update method in the CustomerObserver and LoggingObserver classes will start to be executed. The priority and delay of the implementation of each of these classes are directly related to the order of their entry into the _subscribers list.
Participants:
Subject: In the preceding scenario, it is the same as IShoeNotifier, and the task is to provide a template for registering and canceling the subscription.
Observer: In the preceding scenario, it is the IShoeObserver and is responsible for providing the updated format for subscribers.
ConcreteSubject: In the preceding scenario, it is the ShoeNotifier and is responsible for implementing the template provided by the subject. Also, this class is responsible for sending notifications to observers.
ConcreteObserver: In the preceding scenario, it is the same as CustomerObserver and LoggingObserver and is responsible for implementing the template provided by the observer. This class can store a reference to ConcreteSubject.
Client: It is the same user who executes the code by defining the subject and observer and registering observers in the list of subscribers.
Notes:
This design pattern has similarities with a chain of responsibility, command, and mediator design patterns:
In the chain of responsibility design pattern, a request starts moving along a dynamic chain until one of the members of this chain processes the request.
Command design pattern establishes two-way communication between sender and receiver.
The mediator design pattern removes the direct communication between the sender and the receiver and forces the objects to communicate with each other through the mediator.
The observer design pattern makes it possible to subscribe or cancel a registration object dynamically at runtime.
Regarding the mediator design pattern, it can be said that in this design pattern, the mediator can have the role of publisher. And other components can have the role of Subscriber. If the mediator design pattern is designed and considered this way, then it will be very similar to the observer pattern.
When the ConcreteObserver becomes aware of the changes that have occurred in the ConcreteSubject, it can communicate with the ConcreteSubject again and request additional information from it. Actually, the relationship between the observer and the subject can be pulled or pushed. Each of these methods has its advantages and disadvantages. For example, in the push model, it is assumed that the subject knows the observer's needs and always sends complete information to the observer. On the opposite point in the Pull model, the observer object must be able to determine what has changed without getting help from the subject:
In push communication, the subject sends all the details of the changes to the observer and does not care whether this information will be useful for the observer or not.
In the Pull communication, the Subject object informs the observer of the changes with the minimum possible information, and then the observers who are interested can get the details of the changes by reconnecting with the Subject.
In this design pattern, it is possible for an observer to monitor several subjects. In this case, the important point is which of the subjects is called the update method. A solution for this problem is that the update method itself receives the subject-object as an input parameter so that it can be recognized which subject is called an update on the observer side.
In using this design pattern, it is important to specify how the notify method should be called. There are two ways for this:
After changing any status, the notify method is called automatically: in this case, the notify method may be called several times per work unit, which can be expensive for the observer side. On the other hand, the advantage of this method is that we always know that the notify method will be called after changing the subject status.
The user should have the task of manually calling the notify method: the disadvantage of this method is that the user will find a new task, and that is calling the notify method. Its advantage is that the notify method can be called only once per work unit.
By using the template method design template, it is possible to prevent the sending of change notifications when the subject itself is in an unstable state. In this way, the last method in the template method can be called the notify method. Therefore, this makes us sure that the notify method will always be called at the end of the work and when the subject is in a stable state.
In a complex scenario, if we are dealing with multiple Subjects and want to notify Observers after making changes to all Subjects, then a ChangeManager will be needed. ChangeManager removes the direct connection between the subject and the observer and establishes the connection through an interface. The role of ChangeManager in this pattern is as a mediator, and a singleton pattern can be used in its implementation. In terms of application, ChangeManager also works very similarly to a service bus. To implement ChangeManager, the following code can be considered:
public class ChangeManager
{
private ChangeManager() { }
private static readonly object _lock = new();
private static ChangeManager _instance;
public static ChangeManager GetInstance()
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
_instance = new ChangeManager();
}
}
return _instance;
}
static List
public void Register(IShoeNotifier notifier, IShoeObserver observer)
{
if (!mappings.Any(
x => x.Notifier == notifier &&
x.Observer == observer))
mappings.Add(
new Mapping { Notifier = notifier, Observer = observer });
}
public void Unregister(IShoeNotifier notifier, IShoeObserver observer)
{
var map = mappings.FirstOrDefault(
x => x.Notifier == notifier && x.Observer == observer);
if (map != null)
mappings.Remove(map);
}
public void Notify(Shoe param)
{
foreach (var item in mappings)
{
item.Observer.Update(param);
}
}
}
Also, the subject codes for the proposed scenario are changed as follows:
public class ShoeNotifier : IShoeNotifier
{
private ChangeManager _changeManager;
public ShoeNotifier() => _changeManager = ChangeManager.GetInstance();
public void Subscribe(IShoeObserver param)
=> _changeManager.Register(this, param);
public void Unsubscribe(IShoeObserver param)
=> _changeManager.Unregister(this, param);
public void Notify(Shoe param)
{
_changeManager.Notify(param);
}
}
As can be seen in the preceding code, the connection between the subject and observer is broken, and communication happens only through ChangeManager. There can be two approaches to implementing ChangeManager. The simple approach is to notify all observers of all subjects while notifying them. In this method, an observer may be mapped several times with different subjects, and then the update method will be called several times for that observer. The second method is only to call the unique mappings while calling the update method.
Consequences: Advantages
New subscribers can be defined without changing the existing codes. For this reason, the open/close principle has been observed.
It is possible to establish a relationship between objects during execution.
All the information that the subject has is what observers shared with them, and it has no information about ConcreteObservers. In this way, there is a loose coupling between the subject and the observer.
Consequences: Disadvantages
If it is not clear what has changed on the subject side, this can cause the update method of all observers to be activated and executed, which can be very costly.
Applicability:
When there is a need to change the state of an object, a set of other objects can also be changed, and this design pattern can be used. In this case, the use of this design pattern is more evident when the list of target objects (subscribers) is unclear.
This design pattern can be used when there is a need to have a set of objects, one object, for a short period of time. With the help of subscribe and unsubscribe methods, objects can start and finish the monitoring process.
Related patterns:
Some of the following design patterns are not related to the observer design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:
Chain of responsibility
Command
Mediator
Singleton
Template method
Visitor
Name:
Visitor
Classification:
Behavioral design patterns
Also known as:
---
Intent:
This design pattern tries to perform various operations on an object without changing its structure.
Motivation, Structure, Implementation, and Sample code:
Suppose there is a requirement in which different discounts can be applied on different items of the invoice, according to the type of item and the type of customer loyalty (gold customer and silver customer). Let us assume that this requirement has been raised by a large chain store that sells various products such as food, cosmetics, and household appliances.
To implement this requirement, business related to discounts and customer loyalty types can be done within the invoice class. The problem with this method is that over time and with the identification of other requirements, the invoice class will contain many unrelated codes, which will make it difficult to maintain the code. But there is another solution to this need. In the preceding scenario, we need to navigate the invoice items with the help of a mechanism and apply discounts related to the level of customer loyalty based on the type of item.
In fact, in the preceding scenario, we are facing two different elements. The first element is the various invoice items (food, cosmetics, and household appliances), and the second element is the various operations that must be performed on the invoice items (applying discounts on food items for gold and silver users). We are facing a class called invoice, which consists of a list of various items. Each type of item has its own calculation method. We are also faced with an abstraction called visitor, which has implemented different types of visits, such as visits to apply discounts for gold customers and visits to apply discounts for silver customers, and all types of invoice items benefit from these types of visits.
With the preceding explanations, the following class diagram can be imagined:
Figure%205.6.png
Figure 5.6: Visitor design pattern UML diagram
As can be seen in the Figure 5.6 class diagram, the invoice class consists of a series of items (IInvoiceItems). InvoiceItems include Food, Cosmetics, and HomeAppliances. Each IInvoiceItem provides its implementation of the Accept method and connects to the appropriate visitor. Visitors are under the format of ILoyalityVisitor and include GoldenUsersVisitor to apply discounts for golden customers and SilverUsersVisitor to apply discounts for silver customers. For Figure 5.6 class diagram, the following code can be considered:
public class Invoice
{
private readonly ILoyalityVisitor visitor;
public Invoice(ILoyalityVisitor visitor) => this.visitor = visitor;
public List
public double Calculate() => Items.Sum(x => x.Accept(visitor));
}
public interface IInvoiceItems
{
public double Cost { get; set; }
double Accept(ILoyalityVisitor visitor);
}
public class Food : IInvoiceItems
{
public double Cost { get; set; }
public double Accept(ILoyalityVisitor visitor) => visitor.VisitFoods(this);
}
public class Cosmetics : IInvoiceItems
{
public double Cost { get; set; }
public double Accept(ILoyalityVisitor visitor)
=> visitor.VisitCosmetics(this);
}
public class HomeAppliances : IInvoiceItems
{
public double Cost { get; set; }
public double Accept(ILoyalityVisitor visitor)
=> visitor.VisitHomeAppliances(this);
}
public interface ILoyalityVisitor
{
double VisitFoods(Food item);
double VisitCosmetics(Cosmetics item);
double VisitHomeAppliances(HomeAppliances item);
}
public class GoldenUsersVisitor : ILoyalityVisitor
{
public double VisitCosmetics(Cosmetics item) => item.Cost * 0.9;
public double VisitFoods(Food item) => item.Cost * 0.8;
public double VisitHomeAppliances(HomeAppliances item) => item.Cost * 0.9;
}
public class SilverUsersVisitor : ILoyalityVisitor
{
public double VisitCosmetics(Cosmetics item) => item.Cost * 0.95;
public double VisitFoods(Food item) => item.Cost * 0.9;
public double VisitHomeAppliances(HomeAppliances item) => item.Cost * 0.95;
}
According to the preceding code, the following discount table is in place:
Product
SilverUsersVisitor
GoldenUsersVisitor
Food
10% discount
20% discount
Cosmetic
5% discount
10% discount
Home Appliances
5% discount
10% discount
Table 5.1: Customers discount table
As it is clear in the preceding code, each visitor (GoldenUsersVisitor and SilverUsersVisitor) has provided their implementation for the methods related to Foods, Cosmetics, and HomeAppliances. These types of items have also tried to provide an implementation for the Accept method, through which you can visit the invoice items of customers and perform the relevant calculations. In the Invoice class, if you pay attention, you can see that in the Calculate method, the invoice items are traversed, and for each item, the Accept method is called, and the visit is accepted.
To use this structure, you can use the following code:
Invoice invoice = new(new GoldenUsersVisitor());
invoice.Items.Add(new Food() { Cost = 100 });
invoice.Items.Add(new Cosmetics() { Cost = 150 });
invoice.Items.Add(new HomeAppliances() { Cost = 1000 });
Console.WriteLine($"Total: {invoice.Calculate()}");
Participants:
Visitor: In the preceding scenario, it is the ILoyalityVisitor, which is responsible for providing the template for visiting each ConcreteElement in an object structure. Usually, in the definition of this format, the signature of the method and its name indicate the sender of the visit request. For example, in the preceding scenario, the name and signature of the FoodVisitor(Food item) method indicate that the visit was requested by Food.
ConcreteVisitor: In the preceding scenario, it is GoldenUsersVisitor and SilverUsersVisitor, which is responsible for implementing the template provided by Visitor.
Element: In the preceding example, it is the same as IInvoiceItems, which is responsible for defining the format of the Accept method. This method must receive visitors as input.
ConcreteElement: In the preceding scenario, it is the same as Food, Cosmetics, and HomeAppliances and is responsible for implementing the template provided by Element.
ObjectStructure: In the preceding example, it is the same as Invoice and is responsible for defining the object structure.
Client: It is the same user who executes the code by providing ObjectStructure and Visitor.
Notes:
This design pattern can be used along with the iterator design pattern to navigate complex structures such as trees.
This design pattern can be used to interpret the interpreter design pattern.
In the implementation of this design pattern, it should be noted that the accept method, which is located in the object structure, uses the double dispatch technique. It means calling the accept method requires the exact type of visitor and element, each of which is determined at runtime.
Single Dispatch: We want to call a virtual method that is overridden in the child class. We can call this method through an object of the child or parent class. This mode is called Single Dispatch.
1. public class Base{ public virtual void X(){} }
2. public class Child: Base{ public override void X(){} }
3. //…
4. Base obj = new Child();
5. obj.X();
Method X requires a type obj in order to run. Here, obj is of Child type, so the X method in the Child class will be called.
Double Dispatch: Consider the following code:
public class Base
{
public class Class1 { }
public class Class2 { }
public virtual void Method(Class1 obj) { }
public virtual void Method(Class2 obj) { }
}
public class Derived : Base
{
override public void Method(Class1 obj) { }
override public void Method(Class2 obj) { }
}
//…
Base b = new Derived();
var objs = new object[] { new Class1(), new Class2() };
Now we need to traverse the objs array and call the Method for each of its members. To call the method, we need two things to be clear. First, we need to know the type of object b, in which the preceding code is of Derived type. We also need the type of Method input parameter. This mode is called double dispatch. That means we need two elements to call the method. C# language supports double dispatch through a feature called dynamic typing. Therefore, by using this feature, the stated requirement can be implemented as follows:
foreach (dynamic item in objs)
{
b.Method(item);
}
In the preceding code, the type of item will be specified at the time of execution, and the appropriate method will be called at each for each traversing. In fact, double dispatch is a type of multiple dispatch.
Now, according to the above explanations, in the Visitor design pattern, by using the Double Dispatching technique (Which is used in calling the Accept method), different Visitors can visit one type, and different types can be visited by one Visitor:
Public class Invoice
{
private readonly IloyalityVisitor visitor;
public Invoice(IloyalityVisitor visitor)
=>this.visitor = visitor;
public List
= new List
public double Calculate() => Items.Sum(x => x.Accept(visitor));
}
That is, in the above code, the execution of the Accept method depends on the type of x and visitor, both of which will be specified at the runtime.
Consequences: Advantages
It is possible to define new behaviors without making changes in the existing codes, and hence Open/Close principle has been observed. For example, you can define the mechanism for bronze customers without changing the codes of gold and silver customers.
Due to the separation of different types of behavior (visiting food items, visiting cosmetics, and so on), a single responsibility principle has been observed.
The object that performs the visit can save information about that visit every time it is visited. This possibility can be very useful in visiting complex structures, such as trees.
This design pattern collects related operations in visitors and prevents these operations from spreading to different classes. Also, unrelated operations are placed in visitors specific to that operation, and in this way, for example, a specific algorithm for traversing a data structure is placed in the corresponding visitor.
Consequences: Disadvantages
When a new class is created, or a class is deleted, the visitor must be updated, and hence it is not easy to define a ConcreteElement.
Since visitors do not have access to private methods, visiting operations may face problems when these methods are needed.
This design pattern often tries to access the status and internal data of an element by providing public methods. This causes the encapsulation to be damaged.
Applicability:
Performed a series of operations on the members of a complex object when needed.
When necessary, separate the codes related to complementary businesses from the main code. In the preceding scenario, applying discounts based on customer loyalty is a complementary business to the main business. The main business in the preceding scenario was selling products.
Related patterns:
Some of the following design patterns are not related to the visitor design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:
Iterator
Interpreter
Composite
Conclusion
In this chapter, you got acquainted with the rest of the behavioral design patterns and learned how to easily perform complex tasks such as traversing a collection, notifying changes, saving object data, and so on using behavioral design patterns. This chapter was the last chapter about GoF design patterns, and from the next chapter, you will learn about PoEAA design patterns.
Join our book's Discord space
Join the book's Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:
https://discord.bpbonline.com