Category Archives: C#

NET 7 Design Patterns In-Depth 11. Web Presentation Design Patterns

Chapter 11
Web Presentation Design Patterns
Introduction
To organize the web presentation, the design patterns of this category can be divided into the following seven main sections:

Model view controller: Designing User Interface (UI) interactions are divided into three different roles.
Page controller: An object handles requests for a page or action on a website.
Front controller: A controller is responsible for managing all incoming requests.
Template view: By placing a series of markers in HTML pages, various information can be displayed on the pages at runtime.
Transform view: It can process the existing data and convert it to the user's preferred format.
Two-step view: The data in the model is converted into HTML format in two steps.
Application controller: It can centrally manage the movement between pages and the overall flow of the program.
Structure
In this chapter, we will cover the following topics:

Web presentation design patterns
Model view controller
Page controller
Front controller
Template view
Transform view
Two-step View
Application Controller
Objectives
In this chapter, you will learn how to organize the user interface. You will also learn about design patterns in this field. In this chapter, you will learn how to separate the data, logic, and display parts from each other so that each part can be implemented in a useful and appropriate way.

Web presentation design patterns
One of the most significant changes in enterprise applications in recent years is the penetration of web-based user interfaces. These interfaces come with various advantages, including that the client often does not need to install a special program to use them. Creating web applications is often accompanied by the generation of server-side codes. The request is entered into the web server, which delivers the request based on the web application's content or the corresponding website.

The model view controller design pattern can separate the display-related details from the data structure and logic. In this structure, the controller receives the request, and the data is received from the data source by the model. Then, based on the results, the controller returns the appropriate view in the response, and the user sees the desired view.

Sometimes, a central engine is needed in the structure to manage the movement between different pages. In this case, you can use an application controller. If there is different and complex logic for the order between the displays in the program, the application controller can have a significant impact.

You can benefit from three different design patterns for the view section: Transform view, template view, and two-step view. Markers are used in template view and then replaced with dynamic content. You can use transform view to create a view based on the domain model. Both patterns provide the possibility of single-step views. But sometimes, it is necessary to first convert the data into a logical representation, then convert the logical representation into the view desired by the user. In this case, a two-step view will be useful.

The next part to pay attention to in the discussion of web display is to pay attention to the controllers. One method for controllers is to have one controller per page on the website. This method, which is the simplest type, can be implemented through a page controller. But usually, having a controller for one page will complicate the code. Most of what the controller does can be divided into two parts. Part 1) receiving the HTTP request and part 2) deciding what processing should be done for an incoming request. Therefore, it is better to separate these two parts from each other. In this regard, you can get help from Front Controller.

Model view controller
Name:

Model view controller

Classification:

Web presentation design patterns

Also known as:

MVC

Intent:

Using this pattern, the design of user interface interactions is divided into three different roles, which are view, model, and controller:

Model: It is an object that provides some information about the domain. All the data and behaviors related to them are presented through the model. In an object-oriented approach, the model can be considered an object within the domain model.
View: Data can be displayed in the UI through this section. The only task of this section is to display information and manage user interactions. In web applications, this section usually includes HTML codes.
Controller: Through this section, the data is received from the view, placed in the model, and it causes the view to be updated accordingly.
Motivation, Structure, Implementation, and Sample code:

With the preceding explanation, UI can be considered a combination of view and controller. We are planning to design a login form, and we intend for the user to enter his/her page by entering his/her username and password and see his/her username and password next to his/her name.

According to the preceding requirement, the model class can be considered as follows:

public class LoginModel

{

public string UserName { get; set; }

public string Password { get; set; }

public string FullName { get; set; }

}

As you can see, the Model class has three properties for UserName, Password, and FullName.

The codes related to the controller can also be considered as follows:

public class UserController

{

private IView view;

public IView LoginIndex()

{

view = new LoginView();

view.Display();

return view;

}

public IView DashboardIndex(LoginModel model)

{

if (model == null)

return LoginIndex();

else

{

model.FullName = "Vahid Farahmandian";

view = new DashboardView(model);

view.Display();

}

return view;

}

public IView Login(LoginModel model)

{

if (model.UserName == "vahid" && model.Password == "123")

return DashboardIndex(model);

else

return LoginIndex();

}

public IView Logout() => LoginIndex();

}

In the preceding code, the LoginIndex method is responsible for returning the view related to Login. The login form is displayed to the user when this method is called. The DashboardIndex method is like this one and returns the View related to the user panel. Besides these two methods that return views, there are also two other methods. By receiving the user's inputs, the Login method performs the necessary validations and decides whether to log in to the user or not. The Logout method also receives the user's request and, while removing the user from the user account, returns him/her to the login page. As it is clear in the controller codes, the controller communicates with the model by receiving the user's inputs, values the object related to the model, and establishes the connection between View and Controller.

The codes related to View can also be imagined as follows:

public class LoginView : IView

{

readonly LoginModel model;

readonly UserController controller;

public LoginView()

{

model = new LoginModel();

controller = new UserController();

}

public void Display()

{

Console.WriteLine($"Enter username:");

model.UserName = Console.ReadLine();

Console.WriteLine($"Enter password:");

model.Password = Console.ReadLine();

}

public IView Login() => controller.Login(model);

}

The preceding codes show the codes related to the login view. This class has a method named Display which displays the corresponding form by calling it. Also, after entering the username and password, the user sends the information to the controller by pressing the login button. The Login method is embedded in this class to simulate this. The codes related to the view of the user panel will be similar to these codes. As follows:

public class DashboardView : IView

{

readonly LoginModel model;

readonly UserController controller;

public DashboardView(LoginModel model)

{

this.model = model;

controller = new UserController();

}

public void Display()

=> Console.WriteLine($"Username: {model.UserName},

Password: {model.Password},

FullName: {model.FullName}");

public IView Logout()

{

Console.WriteLine("User logged out successfully");

return controller.Logout();

}

}

As it is clear in the preceding codes, View receives data from the user and sends it to the Controller or takes it from the Controller and displays it in the output.

Note that in the preceding code, an attempt has been made to show the various components of the MVC design pattern in the form of C#.NET codes with a simple example.

Notes:

Transaction script can be considered a type of model because we have nothing to do with the user interface in writing transaction script.
Using the Microsoft ASP.NET MVC framework, web applications can be created using the MVC design pattern.
This design pattern can be used in non-web applications as well.
This design pattern indicates two types of separation. The first is to separate the View from the model, and the second is to separate the Controller from the View. It is very important to separate the model from the View. One of the reasons for this is that the way of display and the work related to the model are two different concerns and should not be addressed simultaneously. Also, this separation allows a model to be presented to the end user in different ways (Web Page, Web API, and so on) and in different formats (login form, user panel form, and so on). Separating the controller from the view is less important today, with different frameworks for UI. The role of the controller is changing, and most of the controller part is placed between the view and the model. Application controller design patterns can be very helpful in this case to clarify the issue.
Combining this design pattern with the observer design pattern can be very useful. In this case, the view can be updated automatically by applying changes to the model.
Consequences: Advantages

Due to the separation of the view from the model, it can be tested independently.
Using this design pattern, the complexity can be better managed with the help of three divisions.
It is easier to maintain and develop the code in general.
The principle of Separation of Concern (SoC) is better observed.
Consequences: Disadvantages

The amount of code in the controller can be large and threaten the code's maintenance.
It is more difficult to read and make changes to the code.
Applicability:

Except in small systems, where the model does not contain specific behavior, in the rest of the systems, using this design pattern can be very useful.
Related patterns:

Some of the following design patterns are not related to the Model View Controller design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Transaction script
Application controller
Observer
Page controller
Name:

Page Controller

Classification:

Web presentation design patterns

Also known as:

---

Intent:

Using this design pattern, an object handles the requests of a page or action on a website. In fact, in this design pattern, there is a controller for each page.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a requirement, and, in its format, we need to display the user's name and surname after receiving the user's national code. Using the page controller design pattern, there are two parts to implementing this requirement. The first part is the view part, and the second is the business logic part. The logic section is the combination of the controller and model.

Suppose we want to use ASP.Net web forms to implement this requirement. Using this technology, the view section is the same as the web form pages, which are composed of HTML codes, and the logic section is the code behind it. Now, to implement the preceding requirement, the following codes can be imagined for the View section:

<%@Page Language="C#" Codebehind="Inquiry.aspx.cs" AutoEventWireup="false" Inherits="Inquiry" %>

National Code:

As it is clear in the above code, in the first line, it is specified in which file the logic related to the above View is located. (CodeBehind="Inquiry.aspx.cs"). It is also clear from the preceding code that when the btnInquiry button is clicked, which method should be executed on the server side. The codes related to Code Behind are as follows:

public class Inquiry : System.Web.UI.Page

{

protected System.Web.UI.WebControls.TextBox name;

protected System.Web.UI.WebControls.Button MyButton;

protected System.Web.UI.HtmlControls.HtmlGenericControl mySpan;

public void btnInquiry_Click(Object sender, EventArgs e)

{

var info = DatabaseGateway.Find(txtNationalCode.Text);

if(info==null)

result.InnerHtml="Not found";

result.InnerHtml = $"First name and Last name:

{info.Name} {info.LastName}";

}

}

According to the preceding code, when the user enters the national code in txtNationalCode, he/she clicks on the inquiry button. By clicking on this button, the Click event related to the button is called, and in response to this event, the btnInquiry_Click method is called. In this method, the national code value is read, the information is fetched from the database using the DatabaseGateway helper class, and the result is sent to view again.

Notes:

The front controller design pattern and this design pattern describe two different methods for implementing the controller part in the MVC pattern.
The page controller design pattern concept is implemented by default in ASP.NET.
When using this design pattern, duplicate codes will also increase over time and as the number of pages increases. One class can be used as the parent of all classes to reuse codes, and common codes can be transferred to that class. For example, as follows:
Public class Inquiry: BaseController{…}
The preceding code has transferred the repeated codes to the BaseController class.
There are usually two categories of codes in Code Behind. Codes depend on the graphical interface, and codes are independent of the interface. It is usually possible to transfer independent codes from the graphical interface to the parent class. You can benefit from the template method design model to better implement this relationship.
Using the front controller design pattern is better to better manage the complexity in larger programs.
Consequences: Advantages

Since this design pattern is implemented by default in ASP.NET, you can use the features in the framework with minimal effort.
Since each controller has only one page, the codes in the controller include a smaller range of the program and increase simplicity.
By using parent controllers, the possibility of code reuse increases.
By using helper classes, you can develop tasks that can be done in the controller. For example, a helper class can communicate with an information link. Along with inheritance, this method is another method to increase code reusability.
Consequences: Disadvantages

Testing using this design pattern is difficult. Especially if the ASP.Net web form is used, the controllers must have inherited the System.Web.UI.Page class, and it will not be easy to sample from this class. In this case, the only possible way to test is to send an HTTP request and check the received response, which is also prone to errors.
As the number of pages increases or the program becomes more complex and requires more dynamic settings for pages, this design pattern will not be suitable for larger and wider programs.
The use of inheritance to increase reusability, along with all its benefits, makes the code more complicated. Over time, the inheritance hierarchy also becomes more complicated, and the maintenance of the code decreases greatly.
Applicability:

It can be useful for implementing small systems or systems with simple business logic.
Related patterns:

Some of the following design patterns are unrelated to the Page Controller design pattern. To implement this design pattern, checking the following design patterns will be useful:

Front controller
Model view controller
Template method
Front controller
Name:

Front Controller

Classification:

Web presentation design patterns

Also known as:

---

Intent:

Using this design pattern, a controller is responsible for handling all incoming requests. This controller is sometimes called a handler.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement is raised and requested in its format that before responding to any request, the request log is recorded first and then checked whether the user is authenticated in the system. If it was authenticated, their request will be processed; otherwise, an error message will be displayed to the user.

Let us also assume that we have two different controllers to manage requests related to books and publishers, called BookController and AuthorController:

public class BookController

{

public void Get()

{

Console.WriteLine("Design Patterns in .NET");

Console.WriteLine("C# Programming");

}

}

public class AuthorController

{

public void Get()

{

Console.WriteLine("Vahid Farahmandian");

Console.WriteLine("Ali Rahimi");

Console.WriteLine("Reza Karimi");

}

}

To implement the preceding requirement, a simple way is to register the log and check the authentication at the beginning of the methods of each of these controllers. This method has several important drawbacks, including writing repetitive codes and difficulty applying changes.

But another solution is that a controller receives all the requests, registers the log, performs the user authentication process, and finally sends the request to the appropriate controller.

For this, we first define a Dispatcher. Its duty is to direct the request to the controller after receiving the request as follows:

public class Dispatcher

{

BookController bookController;

AuthorController authorController;

public Dispatcher()

{

bookController = new BookController();

authorController = new AuthorController();

}

public void Dispatch(string request)

{

if (request.Contains("/book/"))

bookController.Get();

else

authorController.Get();

}

}

As it is clear in the preceding code, the Dispatch method receives the request and, according to that, directs the request to one of the BookController or AuthorController controllers.

Now that the Dispatcher has been implemented, the front controller is needed to receive the requests and perform log registration and authentication operations. For this, we define the front controller as follows:

public class MainHandler

{

private Dispatcher dispatcher;

public MainHandler() => dispatcher = new Dispatcher();

private bool IsAutheticated() => true;

private void SetLog(string request)

=> Console.WriteLine($"Request received: {request}");

public void ReceiveRequest(string request)

{

SetLog(request);

if (IsAutheticated())

dispatcher.Dispatch(request);

else

throw new Exception("Unauthenticated user error");

}

}

As seen in the preceding code, all requests are first entered into the MainHandler. Within this class and the ReceiveRequest method, we perform tasks related to log registration and authentication verification. If everything is correct, the request will be delivered to the Dispatch method in the Dispatcher class, and this method will deliver the request to the appropriate Controller. The preceding example is a very simple example of this design pattern.

Notes:

Combining this design pattern with the decorator design pattern can be very useful.
If the only reason for choosing this design pattern is to reduce the amount of code, then probably using the page controller design pattern and benefiting from inheritance to manage the duplicate code will be a better solution.
The dynamic approach can also be used in the design of the Dispatcher class. In this method, the target classes can be dynamically identified during execution, and the request can be directed to them.
Consequences: Advantages

Using this design pattern reduces the volume of repetitive codes, and it is easy to make changes.
Consequences: Disadvantages

Compared to the page controller design pattern, it will be more complicated and, therefore, unsuitable for simpler scenarios.
Applicability:

If it is necessary to perform an operational request such as authentication control, access control, and so on, then using this design pattern can be useful.
Related patterns:

Some of the following design patterns are unrelated to the Front Controller design pattern. To implement this design pattern, checking the following design patterns will be useful:

Decorator
Page controller
Template view
Name:

Template View

Classification:

Web presentation design patterns

Also known as:

---

Intent:

Using this design pattern, placing a series of markers in HTML pages, various information can be displayed on the pages at runtime.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a requirement, and we want to display the list of authors on the page as a table. We also want to display the author's name with the most books on the table. When the content we want to present is static, there is no problem designing the view section. The complexity starts when the content of the view section itself becomes dynamic, and the content of this section needs to be the result of a series of calculations or processes.

There are different methods for this. One suitable method is to design the view with the help of a series of markers in the view section and then move these markers with real data in the controller section and use helper classes for each view. To implement this requirement, we consider the following model:

public class Author

{

public string FirstName { get; set; }

public string LastName { get; set; }

public int BooksCount { get; set; }

}

In the preceding model, the BooksCount feature displays the number of books by each author. To get the list of authors and the number of their books, refer to the following code to get help from the Helper class :

public class AuthorHelper

{

public List GetAuthors()

{

return new List()

{

new Author

{

FirstName="Vahid",

LastName="Farahmandian",

BooksCount=2

},

new Author

{

FirstName="Ali",

LastName="Rahimi",

BooksCount=1

},

new Author

{

FirstName="Hassan",

LastName="Abbasi",

BooksCount=3

}

};

}

}

In the GetAuthors method, you can connect to the database and get the list of authors from the database. ASP.NET web form has been used to design the View in this scenario so that the role and effect of the indicators can be easily displayed. With these explanations, View can be considered as follows:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AuthorsList.aspx.cs" Inherits="WebApplication1.AuthorsList" %>

Best Author:

As shown in the preceding HTML code, inline expressions in .NET have been used as pointers to set the fistName and lastName labels. The code corresponding to the preceding View To set the BestAuthor attribute is also defined as follows in the Code Behind:

protected Author BestAuthor { get; set; }

protected void Page_Load(object sender, EventArgs e)

{

var helper = new AuthorHelper();

Authors = helper.GetAuthors();

BestAuthor = Authors.OrderByDescending(x => x.BooksCount).FirstOrDefault();

firstname.DataBind();

lastname.DataBind();

}

According to the preceding code, when the page is loaded, the author with the most books is identified, and his information is placed in the BestAuthor feature. Then, the available indicators are placed in the View with appropriate values, and the page is loaded. According to the preceding codes, the final HTML code sent to the user's browser will be as follows:

Best Author:

Hassan

Abbasi

As you can see, the markers in the View are placed with appropriate values. In the following, different ways can be used to display the list of authors. ASP.NET web form has provided a component called DataGrid for the tabular display of data, which can be used in View as follows:

The preceding code shows that the DataGrid will have three columns displaying the author's information. To assign values to this DataGrid, you can do the following in Code Behind:

var helper = new AuthorHelper();

Authors = helper.GetAuthors();

authorsGrid.DataSource = Authors;

authorsGrid.DataBind();

With the preceding description, the code for the View section will be as follows:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AuthorsList.aspx.cs" Inherits="WebApplication1.AuthorsList" %>

Best Author:

Also, the final code in Code Behind will be as follows:

public partial class AuthorsList : System.Web.UI.Page

{

protected List Authors { get; set; }

protected Author BestAuthor { get; set; }

protected void Page_Load(object sender, EventArgs e)

{

var helper = new AuthorHelper();

Authors = helper.GetAuthors();

authorsGrid.DataSource = Authors;

authorsGrid.DataBind();

BestAuthor = Authors

.OrderByDescending(x => x.BooksCount)

.FirstOrDefault();

firstname.DataBind();

lastname.DataBind();

}

}

Notes:

The main idea behind this design pattern is to design HTML pages with a series of markers to provide dynamic content. When these pages are requested, the markers are placed on the server side with appropriate values, and the response is returned to the client.
Codes that contain programming logic and are placed in view are called scriptlets.
You can use Helper classes and implement the desired logic to prevent scriptlet placement on the page. The implementation of this logic is sometimes complicated. For example, it is possible to display male authors in yellow and female authors in blue in a table. Returning the HTML tag instead of the value in the server-side codes is one of the ways to prevent scriptlet placement in the View codes. The problem with this method will be the difficulty of maintaining the code.
To display a data list in View, there are different methods. One of these ways is to write a loop and form a line for each record. Another way is to use the templates provided by the technology used. For example, DataGrid can be useful for this purpose.
To implement the View section in MVC, choose between template view, transform view, and two-step view design patterns.
Consequences: Advantages

If there are not many scriptlets on the page, the graphic design of the pages is possible according to the structure of the pages in a simple way. This makes it easy for graphic designers to design pages.
Consequences: Disadvantages

If many scriptlet codes are placed in View, it will be difficult for non-programmers, such as graphic designers, to understand View.
Testing using this design pattern is difficult.
Applicability:

This design pattern can be used to implement the View section in MVC.
Related patterns:

Some of the following design patterns are not related to the Template View design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Transform view
Two-step view
Model view controller
Transform view
Name:

Transform view

Classification:

Web presentation design patterns

Also known as:

---

Intent:

Using this design pattern, the existing data can be processed and converted into the user's preferred format. The preferred format is usually HTML, but sometimes it may be necessary to convert the data to XML or JSON, or any other format.

Motivation, Structure, Implementation, and Sample code:

Suppose we need to get the list of authors from the database, convert it into HTML format, and display it in the output. Due to the problems of the template view design pattern, we want to use the transform view design pattern for this.

For this, we first need to get the list of authors from the database or any other data source. Therefore, we have the following codes for this:

public class Author

{

public string FirstName { get; set; }

public string LastName { get; set; }

public int BooksCount { get; set; }

}

public class DatabaseGateway

{

public List GetAuthors()

{

return new List()

{

new Author

{

FirstName="Vahid",

LastName="Farahmandian",

BooksCount=2

},

new Author

{

FirstName="Ali",

LastName="Rahimi",

BooksCount=1

},

new Author

{

FirstName="Hassan",

LastName="Abbasi",

BooksCount=3

}

};

}

}

According to the preceding codes, we have received the list of authors. Now, before we deliver this list to View, we need to process it and convert it to the HTML format the user desires. There are different ways to do this. One of the ways is to convert this list into XML format and then convert it to desired HTML format with the help of XSLT. Another simple way is to process the list and generate the HTML tags using a loop. For the sake of simplicity, we will consider the second way here. For this purpose, the desired method can be written as follows:

public string Transform(List authors)

{

StringBuilder sb = new();

sb.AppendLine("");

sb.AppendLine("");

sb.AppendLine("");

sb.AppendLine("

");

sb.AppendLine("

");

foreach (var item in typeof(Author).GetProperties())

{

sb.AppendLine($"

");

}

sb.AppendLine("

");

foreach (var item in authors)

{

sb.AppendLine("

");

sb.Append($"

");

sb.Append($"

");

sb.Append($"

");

sb.AppendLine("

");

}

sb.AppendLine("

{item.Name}
{item.FirstName} {item.LastName} {item.BooksCount}

");

sb.AppendLine("");

sb.Append("");

return sb.ToString();

}

As seen in the preceding method, the data is entered into this method as input, and inside this method, it is converted into the user's desired format. After the output is ready, it can be presented and displayed directly to the user. This method is called Transformer.

Notes:

Using generics, reflection, or nested loops and return methods, you can implement the Transformer method in such a way that it can be used to convert various types of objects to HTML.
If Transformer is defined as generic, it can reuse the code.
Consequences: Advantages

Using this design pattern makes it possible to test better than the Template View design pattern.
Consequences: Disadvantages

Providing views of the most complex design pattern will increase complexity.
Applicability:

This design pattern can be used to implement the View section in MVC.
Related patterns:

Some of the following design patterns are not related to the Transform View design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Template view
Model view controller
Two-step view
Name:

Two Step View

Classification:

Web presentation design patterns

Also known as:

---

Intent:

Using this design pattern, the data in the model are converted into HTML format in two steps. In the first stage, this data structure is converted into a logical structure; in the second stage, this logical structure is converted into HTML.

Motivation, Structure, Implementation, and Sample code:

Suppose we are displaying the list of authors in the form of a table to the user. Besides the list of authors, we also have the list of publishers and books. A requirement has been raised for the table rows to have one of the different background colors. We need to refer to and change each table to apply this change. But there is another solution, and that is to use the two-step view design pattern.

If we can convert existing models into an intermediate logical structure, then we can convert this logical structure into HTML codes using a converter. For this, we first examine the first stage of this design pattern: the production of a logical structure.

There are different methods to generate a logical structure, but we use XML and XSLT methods for easier and better understanding. Suppose we have the authors' data in XML format in the following form:

Vahid"+

Farahmandian

2

Ali

Rahimi

1

Hassan

Abbasi

3

If you are unfamiliar with XML, it is recommended to get familiar with it through the link: https://www.w3schools.com/xml/xml_whatis.asp.

However, to clarify the example, the above XML structure and the following JSON structure are equivalent. Of course, we will proceed with the XML structure in the following explanations:

{

"authors": {

"author": [

{

"firstname": "Vahid",

"lastname": "Farahmandian",

"booksCount": 2

},

{

"firstname": "Ali",

"lastname": "Rahimi",

"booksCount": 1

},

{

"firstname": "Hassan",

"lastname": "Abbasi",

"booksCount": 3

}

]

}

}

We convert the preceding XML structure into an expected logical structure using an XSLT converter to generate a logical structure. The XSLT converter can be defined as follows:

As seen in the preceding XSLT structure, the model previously mentioned in the XML structure is converted into the expected logical format with this structure. For example, in this format, it is said that whenever you reach authors, define the equivalent structure

...

. To apply this XSLT to the said XML, you can use the following code:

public void FirstStepTransformer()

{

var myXslTrans = new XslCompiledTransform();

myXslTrans.Load(@"firststep-style.xslt");

myXslTrans.Transform(@"input.xml", @" logical.xml");

}

The result of executing this code will be the following output:

Vahid"+

Farahmandian

2

Ali

Rahimi

1

Hassan

Abbasi

3

As seen in the preceding output, the format defined in XSLT is applied to the input XML and the output is generated. This output is the first step in the two-step view design pattern. To produce the output of the second step, which is often the output in HTML format, similar steps but with a different XSLT format must be followed. Therefore, we can define the XSLT required to generate the desired HTML as follows:

grey

white

As seen in the preceding code, the output of the first step is converted to HTML using this format. Using this XSLT structure, HTML output will be generated as follows:

Vahid Farahmandian 2
Ali Rahimi 1
Hassan Abbasi 3

As seen in the preceding HTML output, the rows of the table have different background colors among them. In two steps, a series of data were converted to HTML and displayed to the user.

Notes:

The first step in this design pattern does not contain any HTML code. In other words, no code related to the appearance and display is placed in the output of the first stage (including the background color and so on)
In the second stage converter, you can add header and footer pages and other sections.
For websites where each page has its design, it is usually better to use template view or transform view templates.
Consequences: Advantages

It is easy to apply changes, especially graphic changes, on the entire website.
Consequences: Disadvantages

Codes are difficult to read for non-programmers (including graphic designers).
For websites with complex graphic designs for each page, it is usually not appropriate to use this design pattern.
Applicability:

When we want to change the entire design of the pages, using this design pattern will be useful because it will only be necessary to change the XSLTs related to the HTML generation.
You can use this design pattern to implement the View section in MVC.
Related patterns:

Some of the following design patterns are not related to the Two Step View design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Template view
Transform view
Model view controller
Application controller
Name:

Application controller

Classification:

Web presentation design patterns

Also known as:

---

Intent:

By using this design pattern, you can centrally manage the movement between pages and the overall flow of the program.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement is raised and, in its form, it is requested to implement the car rental process. To simplify the presentation this process has been simplified. For this requirement, we have the state diagram as follows:

Figure%2011.1.png
Figure 11.1: Car rental process

According to the Figure 11.1 diagram, the following rules are valid for a car:

When the vehicle is in On Lease status, and the return command is received, the status of the vehicle is changed to In Inventory. In this case, the rented car has been returned and added to the list of available cars. In this case, a page should be displayed to the user, and the information related to the return should be entered.
When the vehicle is in On Lease or In Inventory status, and a damage order is received, the status of the vehicle will be changed to In Repair. In this case, depending on whether the car is rented or in the parking lot, different pages are displayed to the user to enter the information related to the repair.
Receiving wrong commands in different situations will lead to displaying the error page.
There are different ways to implement this scenario. All the preceding rules can be placed in their appropriate controllers. But this method will gradually increase the complexity. A better solution is to receive all incoming requests with the help of the front controller design pattern, process it, and return the corresponding view. according to the incoming request The application controller design pattern has two main tasks:

Identifying what processing should happen.
Identifying which view should be displayed.
According to the preceding description, the following enum can be defined for different situations:

public enum State : byte

{

OnLease=1,

InInventory=2,

InRepair=3

}

According to the preceding explanation, the command design pattern can be used to implement task number 1, which is process detection. Therefore, for this section, we have the following:

public interface IDomainCommand

{

abstract public void run(NameValueCollection @params);

}

public class ReturnDetailCommand : IDomainCommand

{

public void run(NameValueCollection @params)

=> Console.WriteLine("return detail data recorded.");

}

public class IllegalActionCommand : IDomainCommand

{

public void run(NameValueCollection @params)

=> Console.WriteLine("Illegal action requested.");

}

public class LeaseDamageCommand : IDomainCommand

{

public void run(NameValueCollection @params)

=> Console.WriteLine("Lease damage data recorded.");

}

public class InventoryDamageCommand : IDomainCommand

{

public void run(NameValueCollection @params)

=> Console.WriteLine("Inventory damage data recorded.");

}

According to the preceding code, we have defined four commands for four processes. ReturnDetailCommand is responsible for processing the vehicle return. IllegalActionCommand is responsible for processing wrong requests. LeaseDamageCommand is in charge of processing requests related to the repair of the leased car, and finally, InventoryDamageCommand is in charge of processing requests related to the repair of the car in the parking lot.

Now, with commands, we need to define the relationship of each node of the provided status diagram through a structure. To define this class structure, we have considered the following:

public struct ResponseStore

{

public string Command { get; set; }

public State State { get; set; }

public Response Response { get; set; }

}

An object of the ResponseStore class determines what command has been issued (Command)? what state are we in (State)? And what response should be given (Response)? The response that is given should be able to return the request processing class and View. With these explanations, the Response class can be defined as follows:

public class Response

{

private Type domainCommand;

private string view;

public Response(Type domainCommand, string view)

{

this.domainCommand = domainCommand;

this.view = view;

}

public IDomainCommand GetDomainCommand()

=> (IDomainCommand)Activator.CreateInstance(domainCommand);

public string GetView() => view;

}

Using the Response class, you can return an object of the IDomainCommand interface implementing class. You can use the GetCommand method for this. For the sake of simplicity, exception management has not been considered in implementing this method. You can also return the View related to each command using this class. For this, the GetView method is used.

Now that the command and response-related structures are prepared, the IApplicationController interface and the CarLeasingApplicationController class can be implemented as follows.

public interface IApplicationController

{

IDomainCommand GetCommand(string command, NameValueCollection @params);

string GetView(string command, NameValueCollection @params);

}

The preceding interface specifies that each ApplicationController should be able to recognize the command and view associated with the input request. For example, the CarLeasingApplicationController class, which implements this interface, works as follows:

public class CarLeasingApplicationController : IApplicationController

{

private readonly List events = new();

public CarLeasingApplicationController()

{

AddResponse("return", State.OnLease,

typeof(ReturnDetailCommand), "return");

AddResponse("return", State.InInventory,

typeof(IllegalActionCommand), "illegalAction");

AddResponse("damage", State.OnLease,

typeof(LeaseDamageCommand), "leaseDamage");

AddResponse("damage", State.InInventory,

typeof(InventoryDamageCommand), "inventoryDamage");

}

}

As you can see, first, we implement the mentioned rules in the class constructor. For example, consider the following rule:

AddResponse("return", State.OnLease, typeof(ReturnDetailCommand), "return");

According to this rule, if the return command is issued and we are in the OnLease state, the ReturnDetailCommand is tasked with processing the request, and the return page must be displayed to the user.

private Response GetResponse(string command, State state)

=>events.FirstOrDefault(x=>x.Command==command && x.State==state).Response;

private State GetCarState(NameValueCollection @params)

=> (State)Convert.ToByte(@params["state"]);

public IDomainCommand GetCommand(string command, NameValueCollection @params)

=> GetResponse(command, GetCarState(@params)).GetDomainCommand();

In the preceding code snippet, with the help of the GetCommand method, by receiving the issued command (Command) along with the sent parameters of the request, the appropriate class for processing the request is identified:

public string GetView(string command, NameValueCollection @params)

=> GetResponse(command, GetCarState(@params)).GetView();

In the preceding piece of code, with the help of the GetView method, after receiving the issued command (Command) along with the sent parameters of the request, the appropriate View is returned:

public void AddResponse(string command, State state, Type domainCommand, string view)

{

Response response = new(domainCommand, view);

if (events.All(x => x.GetType() != domainCommand))

events.Add(new ResponseStore()

{

Command = command,

Response = response,

State = state

});

else

{

var @event = events.FirstOrDefault(x => x.Command == command);

@event.State = state;

@event.Response = response;

}

}

And finally, using the preceding method, you can save the rules in a set. So far, the application controller has been deployed. To use this structure, as mentioned in the beginning, you can benefit from the front controller design pattern. In this case, the front controller can be considered as follows:

public class FrontController

{

public void ReceiveRequest(Uri requestUrl)

{

IApplicationController controller =

GetApplicationController(requestUrl.AbsoluteUri);

NameValueCollection requestParams =

HttpUtility.ParseQueryString(requestUrl.Query);

IDomainCommand command =

controller

.GetCommand(requestUrl.Fragment.TrimStart('#'), requestParams);

command.run(requestParams);

string view = controller.GetView(

requestUrl.Fragment.TrimStart('#'), requestParams);

Console.WriteLine($"navigating to view: {view}");

}

private IApplicationController GetApplicationController(string requestUrl)

{

if (requestUrl.Contains("/leasing/")|| requestUrl.Contains("/leasing?"))

return new CarLeasingApplicationController();

else

return null;

}

}

According to the preceding code, if the request address contains the leasing value, the ApplicationController related to CarLeasing is returned. Then, based on the input request and its parameters, the values related to the command and view are identified. For example, if a request is entered with the following address:

http://abc.com/leasing?model=bmw&state=2&date=20220101#damage

Since the address contains the leasing value, the Application Controller related to CarLeasing will be selected. Then, according to the defined rules, since the damaged command has been issued and we are now in state 2, that is, InInventory, the InventoryDamageCommand should be processed. The View whose name is inventoryDamage should be returned.

Notes:

You can use the command design pattern to implement the two tasks proposed for this design pattern. You can also benefit from other methods, such as reflection instead of command.
You can use the front controller design pattern to implement the entry point to this design pattern.
If there is no connection between the application controller and the UI, the testing capability of the application controller will be improved.
The application controller can be considered an intermediate layer between the display and domain layers.
To implement more complex programs, several application controllers can be used. For smaller programs, only one application controller will be enough.
Consequences: Advantages

In complex programs, code repetition is avoided by placing the logic related to the program flow in one place, which improves maintainability.
Consequences: Disadvantages

If the flow of the program is simple, using this design pattern has no special advantage and will increase the complexity.
Applicability:

Implementing tasks such as wizards or the like in which different flows are formed based on specific rules and situations can be useful.
Related patterns:

Some of the following design patterns are not related to the Application Controller design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Front controller
Command
Reflection
Conclusion
In this chapter, you got acquainted with the types of design patterns related to web displays and learned how to design and implement the view layer in a suitable way using the design patterns of this category.

In the next chapter, you will get to know the types of design patterns related to Distribution 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

NET 7 Design Patterns In-Depth 10. Object-Relational Metadata Mapping Design Patterns

Chapter 10
Object-Relational Metadata Mapping Design Patterns
Introduction
To organize the object-relational metadata mapping, the design patterns of this category can be divided into the following three main sections:

Metadata mapping: Tries to store information about the object in metadata.
Query object: An object is responsible for creating queries.
Repository: Tries to implement CRUD processes optimally by placing a layer between the data access layer and the rest of the system and providing it to the user.
Structure
In this chapter, we will discuss the following topics:

Object-relational metadata mapping design patterns
Metadata mapping
Query object
Repository
Objectives
In this chapter, you will learn how to store information about objects and make centralized database queries. Next, using the repository design pattern, you will learn how to separate the data source from the rest of the system by placing a layer between the data access layer and the rest of the program. These design patterns are suitable for enterprise applications with extensive and sometimes complex business logic and where it is important to separate business logic concerns from data access and storage concerns.

Object-relational metadata mapping design patterns
When we are producing software, we need to implement the mapping between tables and classes. The software production process will be a process that contains a significant amount of repetitive code, and this will increase production time. To avoid this, you can use the metadata mapping design pattern. In this case, it is possible to avoid writing duplicate codes and extracting relationships from metadata using code generators or techniques related to reflection.

When the necessary infrastructure for creating queries is provided using metadata mapping, queries can be created and presented using query objects. In this case, the programmer no longer needs to know SQL and how to make queries. Now, if all the necessary queries to be sent to the database are presented through the query object, then with the help of a repository, the database can be hidden from the rest of the program.

Metadata mapping
Name:

Metadata mapping

Classification:

Object-relational metadata mapping design patterns

Also known as:

---

Intent:

By using this design pattern, information about the object is stored in the form of metadata.

Motivation, Structure, Implementation, and Sample code:

Often, a series of repetitive codes have been written in the examples that have been reviewed so far. The codes related to the mapping of objects to tables and attributes to columns have been constantly repeated. The metadata mapping design pattern helps avoid writing duplicate codes by extracting and storing the metadata related to the object and storing it. For example, consider the following class:

public class Person

{

public int Id { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public int Age { get; set; }

}

Suppose this class is mapped to the people table in the database. The properties of this class are also mapped exactly to the columns of the people table. With this assumption, to be able to write a query to get the list of all people, the following T-SQL code should be written:

SELECT Id, FirstName, LastName, Age FROM people

Now if we assume that we also have the following class:

public class Car

{

public int Id { get; set; }

public string Name { get; set; }

}

If we assume that this class is also mapped to the cars table in the database, then we will have the following query to get the list of all cars:

SELECT Id, Name FROM cars

After preparing the queries and executing them in the database, when the results are returned, each column's data should be mapped into its corresponding property in the target object to be presented to the user.

What happens in the preceding codes is that some codes must be repeated constantly. Suppose we have the metadata (or, in other words, the required information about the classes) and the list of tables and know how to map the tables and classes while avoiding writing duplicate codes. We can use this metadata to create and execute various queries.

To prepare metadata, we first need to get table and class information. The most important information we need is which class is mapped to which table and which column is mapped to each class property. To do this, we define the DataMap class as follows:

public class DataMap

{

public Type DomainClass { get; set; }

public string TableName { get; set; }

public ICollection ColumnMaps { get; set; }

public DataMap(Type domainClass, string tableName)

{

DomainClass = domainClass;

TableName = tableName;

ColumnMaps = new List();

}

public string? GetKeyColumn()

=>ColumnMaps.FirstOrDefault(x=> x.IsKey)?.ColumnName;

public string GetColumns()

{

StringBuilder sb = new();

if (ColumnMaps.Any())

sb.Append(ColumnMaps.First().ColumnName);

foreach (var column in ColumnMaps.Skip(1))

{

sb.Append($",{column.ColumnName}");

}

return sb.ToString();

}

}

In the preceding class, the DomainClass property is used to store the class information, TableName is used to store the table name, and ColumnMaps is used to store the column mapping information. Also, in this class, the GetKeyColumn method returns the name of the column that is the primary key. Please note that if the primary key is composite, the code of this method must be changed. The GetColumns method also returns a string containing the names of the columns, and this string separates column names with commas.

Now, we need to check how to implement the ColumnMap class. This class maps class properties and table columns to each other:

public class ColumnMap

{

public string ColumnName { get; }

public string PropertyName { get; }

public bool IsKey { get; }

public PropertyInfo Property { get; private set; }

public DataMap DataMap { get; }

public ColumnMap(

string columnName, string propertyName,

DataMap dataMap,bool isKey = false)

{

DataMap = dataMap;

ColumnName = columnName;

FieldName = fieldName;

IsKey = isKey;

Property = DataMap.DomainClass.GetProperty(FieldName);

}

public void SetValue(object obj, object columnValue)

=> Property.SetValue(obj, columnValue);

public object GetValue(object obj) => Property.GetValue(obj);

}

In this class, the ColumnName property is used for the column name, PropertyName is used for the property name, IsKey is used to determine whether the column is the primary key or not, Property is used to store property information to receive and store the value, and DataMap is used to specify the related class. The SetValue method is used to put the returned value from the database into the property, and the GetValue method is used to read the property value and send it to the database.

So far, we have reviewed the codes to prepare and obtain metadata so far. In the following, we will see how these codes can be used. For example, suppose that we want to implement the search operation. The search operation can include creating and sending queries to the database, receiving values, and placing them in the desired object. For this, we check the following codes:

Public abstract class Mapper

{

protected DataMap DataMap { get; set; }

public object Find(TKey key)

{

string query = $"" +

$"SELECT {DataMap.GetColumns()}" +

$"FROM {DataMap.TableName}" +

$"WHERE {DataMap.GetKeyColumn()} = {key}";

var reader = new SqlCommand(query, DB.Connection).ExecuteReader();

reader.Read();

var result = Load(reader);

return result;

}

public object Load(IDataReader reader)

{

var obj = Activator.CreateInstance(DataMap.DomainClass);

LoadProperties(reader, obj);

return obj;

}

private void LoadProperties(IDataReader reader, object obj)

{

foreach (var item in DataMap.ColumnMaps)

{

item.SetValue(obj, reader[item.ColumnName]);

}

}

}

The preceding code defines the Mapper class as an abstract class. This class is responsible for creating queries based on metadata, sending them to the database, receiving the results, and mapping them to the class's properties according to the metadata. The DataMap property in this class specifies which metadata we will work with, and this property is later set by classes that inherit Mapper. The Find method in this class is designed to search for records in the table based on the primary key. In this method, according to the methods available in the DataMap class, the information of the table and columns is read, and a query is made. After creating the query, the query is sent to the database, and in return, the results are sent to the Load method.

The Load method first creates an object of the desired class and then uses the LoadProperties method to set the value of each of the properties of this object based on the columns returned from the database.

According to the preceding codes, the search operation can be performed based on the metadata of the classes. Finally, to perform the search in the Person class, we create the PersonMapper class. This class is responsible for setting Person metadata:

public class PersonMapper : Mapper

{

public PersonMapper() => LoadDataMap();

protected void LoadDataMap()

{

DataMap = new DataMap(typeof(Person), "people");

DataMap.ColumnMaps.Add(

new("personId", nameof(Person.PersonId), DataMap, true));

DataMap.ColumnMaps.Add(

new("firstName", nameof(Person.FirstName), DataMap));

DataMap.ColumnMaps.Add(

new("lastName", nameof(Person.LastName), DataMap));

DataMap.ColumnMaps.Add(

new("age", nameof(Person.Age), DataMap));

}

public Person Get(int personId) => (Person)Find(personId);

}

The LoadDataMap method is placed in the PersonMapper constructor in the preceding code. Since this information is immutable, this method can be executed only during the initial loading of the program. Finally, if we want to communicate with the cars table, it will only be enough to implement the CarMapper class like the PersonMapper class.

To get the metadata of the tables, it is enough to serialize the DataMap property to the desired format. The following is a part of the Person class metadata in JSON format:

{

"DomainClass": "Person",

"TableName": "people",

"ColumnMaps": [{

"ColumnName": "personId",

"PropertyName": "PersonId",

"IsKey": true

}, {

"ColumnName": "firstName",

"PropertyName": "FirstName",

"IsKey": false

}, {

"ColumnName": "lastName",

"PropertyName": "LastName",

"IsKey": false

}, {

"ColumnName": "age",

"PropertyName": "Age",

"IsKey": false

}]

}

Notes:

Metadata can be produced in two different ways at two different times:

The first method is using code generators. Using this method, metadata can be given as input to the code generator. The Mapper classes can be delivered and placed next to the rest of the source code. You can even prepare these codes in the build process before starting to compile. Since code generators create these codes and classes during the Build process, it is unnecessary to include them in the source code controller, such as Git and so on.
The second method is Reflection (as in the example provided). This method gives good flexibility to the codes, and on the other hand, it reduces the efficiency. This loss of efficiency should be checked against the advantage that is provided.
It is often possible to save the generated metadata in XML or JSON format. You can even save the metadata of the tables in the database. When the metadata is stored in an external data source, to use it, in the LoadDataMap method seen in PersonMapper, instead of providing the necessary settings, we just need to deliver the existing metadata to the DataMap.

Consequences: Advantages

Using this design pattern significantly reduces the work and codes required for database mapping.
Adding a new model or table is enough to write the relevant Mapper and present the desired logic for mapping (LoadDataMap) in its format.
Consequences: Disadvantages

Using this design pattern makes the code refactoring process difficult. Because after generating the metadata, if the class's property name changes or the table's column name changes, then the program will face a serious problem.
Applicability:

When faced with many models and we must map each to a table in the database, then using this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to metadata mapping design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Query Object
Query object
Name:

Query object

Classification:

Object-relational metadata mapping design patterns

Also known as:

---

Intent:

By using this design pattern, an object takes the task of creating queries.

Motivation, Structure, Implementation, and Sample code:

Suppose we are implementing a software requirement. The team responsible for generating and implementing this requirement does not have information about how to write queries in T-SQL language. In this case, implementing the requirement will be a bit difficult. The members of this team may not be able to write good queries, which may seriously damage the program's overall efficiency.

In this situation, the way of generating queries can be entrusted to a class, and the users of this class, instead of being related to the table and its columns, face the classes and properties that they have produced in the form of models. As an example, consider the example of the person that we discussed in the metadata mapping design pattern section. The class related to the Person model can be considered as follows:

public class Person

{

public int PersonId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public int Age { get; set; }

}

Suppose we want to generate the following query using the query object design pattern:

SELECT PersonId, FirstName, LastName, Age FROM Person WHERE Age > 33

We learned how metadata can be created and used in the metadata mapping design pattern. Also, in that design pattern, we learned how to map the properties of a class to table columns. The remaining point is how to generate the condition section or WHERE clause. The condition created in the WHERE section is often in the Property Operator Value format (For example, Age > 33).

With these explanations, to generate the WHERE section, we define the Criteria class as follows:

public class Criteria

{

public string @Operator { get; set; }

public string Property { get; set; }

public object Value { get; set; }

public Criteria(string @operator, string property, object value)

{

Operator = @operator;

Property = property;

Value = value;

}

}

As shown in the preceding code, the @Operator property is considered for the operator (such as >, <, =, and so on), the Property is considered for the property name, and the Value property is for the value. According to the preceding sample query, this class should be able to make the condition related to Age > 33. For this purpose, we define the GreaterThan method as follows within the Criteria class:

public static Criteria GreaterThan(string property, int value)

=> new Criteria(">", property, value);

As it is clear in the preceding query, the GreaterThan method returns a Criteria object after receiving the property name and value. Next, we need to convert these created Criteria into an understandable query for the T-SQL language. For this purpose, we define the GenerateTSQL method as follows in the Criteria class:

public string GenerateTSQL(PersonMapper mapper)

{

var columnMap = mapper.DataMap.ColumnMaps

.FirstOrDefault(x => x.PropertyName == Property);

return $"{columnMap.ColumnName} {Operator} {Value}";

}

Upon receiving a PersonMapper type object, the preceding method reads the relevant metadata (this class has already been implemented in the metadata mapping design pattern) and returns the column related to the provided property and the condition statement from the metadata in T-SQL language.

The objects related to the condition section have been created, and the corresponding T-SQL code has been generated. We should be able to assign it to our query. Therefore, we define the QueryObject class as follows:

public class QueryObject

{

public ICollection Criterias { get; set; }

}

The QueryObject class has a property called Criteria, which stores all the conditions of a query. Since the number of conditions in the WHERE section can be more than one condition, this property is defined as an ICollection. Also, the QueryObject class has a method called GenerateWhereClause, which adds various conditions. The implementation of this method will be as follows:

public string GenerateWhereClause()

{

StringBuilder sb = new();

foreach (var item in Criterias)

{

if (sb.Length > 0)

{

sb.Append("AND");

}

sb.Append(item.GenerateTSQL(mapper));

}

return sb.ToString();

}

In the preceding code, for the case of simplicity, different conditions are linked to each other using the AND operator.

According to the preceding codes, the WHERE section is made. To use and run a query, you can define the search method in the Mapper class as follows:

public IDataReader FindByWhere(string where)

{

string query = $"" +

$"SELECT {DataMap.GetColumns()}" +

$"FROM {DataMap.TableName}" +

$"WHERE {where}";

return new SqlCommand(query, DB.Connection).ExecuteReader();

}

Next, using the Execute method in the QueryObject class, you can issue a query execution request:

public IDataReader Execute() => mapper.FindByWhere(GenerateWhereClause());

And also, we can use it as follows:

QueryObject qb = new QueryObject();

qb.Criterias.Add(Criteria.GreaterThan(nameof(Person.Age), 33));

var result = qb.Execute();

As you can see, the programming team can create and execute T-SQL queries using classes and their properties without needing T-SQL knowledge.

Notes:

This design pattern aligns with the interpreter design pattern from the GoF design patterns.
Instead of writing the QueryObject class with all the capabilities, it is recommended to create its capabilities based on the need over time.
The metadata mapping or UnitOfWork design pattern can be very beneficial in using this design pattern.
Today, Object-Relational Mappers (ORMs) do exactly what can be done with query objects, so if you use ORM in your program, you will not need to use this design pattern.
Using this design pattern, the database structure is encapsulated.
Consequences: Advantages

When the database structure is different from the structure of the models, using this design pattern can be useful.
Combining this design pattern with the identity map design pattern can increase efficiency. For example, suppose that the list of all people has already been read from the database, and the result is placed in the identity map. Now, a new query has been created to apply a condition on the list of people. The query can be answered without sending it to the database and only by referring to the data in the identity map.
Consequences: Disadvantages

Usually, producing queries that answer the needs of a program, with this design pattern, can be a time-consuming or complex task, and often using existing tools can be a better option.
Applicability:

When the database structure is different from the structure of the models, using this design pattern can be useful.
If the production team lacks the knowledge to write queries, this design pattern can be useful by producing an interpreter.
Related patterns:

Some of the following design patterns are not related to query object design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Interpreter
Metadata mapping
UnitOfWork
Identity map
Repository
Name:

Repository

Classification:

Object-relational metadata mapping design patterns

Also known as:

---

Intent:

This design pattern tries to implement CRUD1 processes optimally and provide them to the user by placing a layer between the data access layer and the rest of the system.

Motivation, Structure, Implementation, and Sample code:

Suppose it is requested to provide a function through which a new user can be defined in the system, edit and delete existing users, find a specific user, and display the list of users. There are different ways to implement this scenario. One of these ways is to design a class to make appropriate queries. The problem with this type of design is that as new entities are added, it will be necessary to write separate classes for each one. Naturally, we will face many duplicate codes in this method.

Another method is to define the desired operation for working with data in the form of an interface, and each entity implements this interface. In this implementation, each entity defines and sets its appropriate queries. This approach is what the repository design pattern tries to provide. According to the preceding explanations, the following class diagram can be imagined:

Figure%2010.1.png
Figure 10.1: Repository design pattern UML diagram

As shown in Figure 10.1 diagram, the IRepository interface defines the necessary methods for working with data. The user entity in the UserRepository class implements this interface and creates appropriate queries for each operation. With these explanations, the following codes can be considered for the repository design pattern:

public class UserDbSet

{

public static List Users = new()

{

new User() { Id = 1, Name = "Vahid" },

new User() { Id = 2, Name = "Ali" },

new User() { Id = 3, Name = "Reza" },

new User() { Id = 4, Name = "Maryam" },

new User() { Id = 5, Name = "Hassan" }

};

}

public class User

{

public int Id { get; set; }

public string Name { get; set; }

public override string ToString() => $"Id: {Id}, Name: {Name}";

}

public interface IRepository

{

User Find(int id);

List GetAll();

void Add(User user);

void Update(User user);

void Delete(int id);

}

public class UserRepository : IRepository

{

public void Add(User user)

{

if (UserDbSet.Users.All(x => x.Id != user.Id))

UserDbSet.Users.Add(user);

}

public void Delete(int id) => UserDbSet.Users.Remove(Find(id));

public User Find(int id)

{

UserDbSet.Users.ToDictionary(x => x.Id).TryGetValue(id, out User result);

return result;

}

public List GetAll() => UserDbSet.Users;

public void Update(User user)

{

var originalUser = Find(user.Id);

if (originalUser != null)

{

originalUser.Name = user.Name;

}

}

}

Now, to use this Repository, you can proceed as follows:

IRepository repository = new UserRepository();

repository.Add(new User { Id = 6, Name = "Narges" });

repository.Update(new User { Id = 3, Name = "Alireza" });

repository.Delete(4);

Console.WriteLine(repository.Find(1));

foreach (User user in repository.GetAll())

{

Console.WriteLine(user);

}

As can be seen, to work with data, the Client does not need to engage with queries and write queries and submits his request only through the Repository of the data source.

If we pay attention to the interface code of IRepository, we will notice that this Repository receives a User object in a method like Add. This method will create a problem: if we need to define a Repository for many entities, we will need to define a separate interface for each. To solve this problem, the repository design pattern can be implemented using the concept of Generics in the C#.NET programming language. For this purpose, the preceding code can be rewritten as follows:

public interface IRepository

{

TEntity Find(TKey id);

List GetAll();

void Add(TEntity user);

void Update(TEntity user);

void Delete(TKey id);

}

Instead of being dependent on the user, the IRepository interface receives the required types at runtime. This interface has two generic types in the preceding code. TEntity is the entity we want to add or edit to the data source. TKey is also a data type of the entity key field, with the help of which we find or delete the entity from a data source. To have a better interface, you can also define a limit on the above Generic types to prevent possible runtime errors. According to the above interface, the UserRepository class will be rewritten as follows:

public class UserRepository : IRepository

{

// Class implementation…

}

With the preceding change applied to the IRepository definition, defining an interface for each entity is unnecessary.

The next point about this design pattern is that implementing the IRepository interface methods may be consistent across all entities. In any case, defining the Repository associated with each entity will be necessary. To prevent this from happening, the entire Repository implementation can be done using Generic. For this purpose, it will be necessary to define a general class that implements the IRepository interface and provides the implementation of the methods. Then any entity that needs a repository should use this class. If an entity needs its Repository or needs to make changes in the default implementation of the provided Repository, it can do so by using inheritance.

With these explanations, the Repository implementation can be changed as follows:

public class Repository : IRepository

{

DbSet _set;

public Repository()

{

_set = DbContext.Set();

}

//…

}

As seen in the preceding code, the Repository class is also defined as Generic, like the IRepository interface. In the implementation of this class, the important point is that with the help of the Set method provided by the Entity Framework, by providing the table type, you can access the corresponding object (_set object). When this object is available, it is easy to implement various data operations. This way of implementing the repository design pattern is called a generic repository. By implementing this pattern in the generic repository method, there will often be no need to define an interface or class for each entity, and the amount of code will be significantly reduced.

Notes:

Today, Entity Framework has implemented the Repository design pattern within itself. The question arises: Although this design pattern is implemented in the entity framework, do we need to re-implement it in our codes? The answer to this question can be very challenging. But it seems that for small systems, re-implementing the Repository design pattern may not be justified, but for larger systems, placing an additional layer between the client and entity framework can be very useful. Some of the advantages of this method are:
LINQ queries will enter the business code if the client and entity framework is directly connected, increasing the dependency between the two layers. While in the presence of a repository layer between these two layers, all queries will be placed inside this intermediate layer, and loose coupling between the business layer and the data access layer will happen.
When we need to write unit tests in the presence of the Repository, we can simply connect the tests to a mock repository and write and run the tests easily.
This design pattern is very similar to the query object design pattern, and combining query objects with metadata mapping produces the required queries.
In using the repository design pattern, it is not necessary to have relational databases.
Usually, this design pattern is used next to the UnitOfWork design pattern.
Consequences: Advantages

The codes related to the business layer are separated from the data access layer. As a result, if the data source needs to be changed, applying this change will be accompanied by minimal changes in the code.
Using this approach, codes can be tested more easily.
Repetitive queries will be reduced during the program.
Separation of Concern (SoC) is observed to separate the business logic from data access.
Consequences: Disadvantages

Considering that an additional abstraction layer is created, this design pattern for small programs can increase complexity.
Applicability:

This design pattern can be useful when there is a need to separate the business logic layer from the data access layer.
Using this design pattern can be very useful for large and complex programs.
Related patterns:

Some of the following design patterns are not related to the repository design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Query object
Metadata mapping
UnitOfWork
Conclusion
In this chapter, you learned how to extract class metadata using different methods, such as reflection. You also learned how to focus the creation of T-SQL queries (Or any other database-related queries) with the help of query object design patterns. Also, in this chapter, you learned how to create a layer between the data access layer and the rest of the system and use it to communicate with the data source to perform various CRUD operations.

In the next chapter, you will learn about Web Presentation design patterns.

1 Create-Read-Update-Delete

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

NET 7 Design Patterns In-Depth 9. Object-Relational Structures Design Patterns

Chapter 9
Object-Relational Structures Design Patterns
Introduction
To organize the object-relational structures, the design patterns of this category can be divided into the following ten main sections:

Identity field: Tries to maintain the data identity between the object and the database record by storing the value of the database identity column(s) inside the object.
Foreign key mapping: Tries to map the foreign key relationship between tables in the database as a relationship between objects.
Association table mapping: Tries to map the many-to-many relationship between tables in the database as relationships between objects.
Dependent mapping: Attempts to make a class responsible for managing child class communication with the database.
Embedded value: It can map an object to several table columns related to another object.
Serialized LOB: It can convert a graph of objects into a LOB (Large Object) and store it in a database field.
Single table inheritance: It mapped the classes that participated in the inheritance structure to a table.
Class table inheritance: Tries to map each class to one table in the database in an inheritance structure.
Concrete table inheritance: Tries to map each non-abstract class to one table in the database.
Inheritance mappers: Tries to organize the various Mappers to manage the inheritance structure.
Structure
In this chapter, we will cover the following topics:

Object-relational structures design patterns
Identity field
Foreign key mapping
Association table mapping
Dependent mapping
Embedded value
Serialized LOB
Single table inheritance
Class table inheritance
Concrete table inheritance
Inheritance mappers
Objectives
In this chapter, you will learn how to map different relationships between database tables (such as one-to-many and many-to-many relationships) to classes. In this chapter, you will learn how to map an inheritance structure to tables in a database. Also, you will learn how to work with dependent data and map them with the data in the database tables.

Object-relational structures design patterns
There are important points to pay attention to when mapping relationships. First, to associate an object with a record in a table, it is often necessary to insert a primary key value into the object. For this, using the identity field design pattern will be useful. Now, one table may be related to another through a foreign key relationship, and the foreign key mapping design pattern can be used to implement this relationship between objects.

The connection between objects may be very long or circular. Therefore, when loading one of the objects, it is necessary to fetch the data related to the dependent object and repeat this process continuously. As mentioned earlier, a lazy load design pattern can be useful in this section. But there is a situation where there is a one-to-many relationship between A and B, and B's data can only be used through A, and we have nothing to do with them outside of A. In this case, A can be considered the owner of B and benefit from the dependent mapping design pattern. This type of communication becomes complicated when the one-to-many relationship becomes a many-to-many relationship. In this case, you can use the association table mapping design pattern.

As mentioned earlier, identity field design patterns can be used for communication between objects. But some data should not be mapped to specific tables, and value objects are among these data. The embedded value design pattern is a better way to store value objects. Sometimes the amount of data that must be stored in this method can be very large. To solve this problem, you can use the serialized LOB design pattern.

The preceding points were related to composition-type relationships. Sometimes there may be an inheritance relationship between classes. This type of relationship does not exist in relational databases, so there are three options for implementing this type of relationship: one table for the entire inheritance structure (Single table inheritance design pattern), one table for each non-abstract class (Concrete table inheritance design pattern), and finally one table for each class (Class table inheritance design pattern).

Identity field
Name:

Identity field

Classification:

Object-relational structures design patterns

Also known as:

---

Intent:

This design pattern tries to maintain the data identity between the object and the database record by storing the value of the database identity column(s) inside the object.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement is raised and it is necessary to retrieve the data in the books table or to define a new book. Suppose the following structure exists for the books table in the database:

BookId

Title

PublishDate

1

Design Patterns in .NET

2021-10-01

2

Programming in C#.NET

2021-11-01

Table 9.1: Book table in the database

When using the identity field design pattern, we store the identity value of the books table in the object of the Book class. This identifier value must have an important feature, and that is that its value must be unique. The reason for this requirement is that we must be able to uniquely identify each record and each object with the help of this value. The best candidate for an identifier will often be the primary key value since the primary key always has a unique value. With this assumption, the code for the Book class will be as follows:

public class Book

{

public int BookId { get; set; }

public string Title { get; set; }

public DateOnly PublishDate { get; set; }

}

As it is clear from the code related to the Book class, the BookId property will be used to store the value of the BookId column of the database. Based on this structure, the following objects can be considered:

Book book1 = new()

{

BookId = 1,

Title = "Design Patterns in .NET",

PublishDate = new DateOnly(2021, 10, 01)

};

In the preceding code, the value of the BookId property of the book1 object is equal to 1, so this object refers to the record with the value of the bookId=1 column in the Books table.

To insert a new book, you can do the following:

public bool Create(Book book)

{

book.BookId = GetNewBookId();

return Save(book);

}

private int GetNewBookId()

{

/*

* In this section, the desired method to generate the

* new value of the key must be implemented.

*/

}

According to the type of key (simple or composite) and the selected method to generate the new value of the key, the GetNewBookId method should be implemented.

Notes:

Key selection is very important in this design pattern. There are two main options for choosing a key. Using a meaningful key and using a meaningless key:
Meaningful key: It is also called natural key; its value is always unique and has a meaning for the user. For example, the ISBN is a unique value for a book and has a meaning for the end user.
Meaningless key: It is also called a generated key. It is a key whose value is unique but has no meaning for the user. For example, the primary key column, which is automatically and sometimes randomly set, is often a meaningless key for the user.
Choosing between these two types of keys is important. Although the meaningful key has a unique value, it has an important shortcoming. It is expected that the key value is immutable and unique. Immutability is not necessarily true for a meaningful key because a mistake may occur when entering its value, and the user wants to edit it. In this case, the immutability condition for the key will be violated. The value of the key can be changed until the changes are sent to the database, and the database has not checked the uniqueness of the value; the object can be used. This can affect reliability. For small scenarios or special situations, a meaningful key can be useful.

The keys can be defined as simple or composite according to their nature. For example, in the database design, the bookId column may be available and has the role of the primary key. In this case, the key is simple. In another case, for example, for the sales information table, storeId and bookId columns together maybe keys. In this case, it is a composite key. Managing uniqueness and immutability constraints for composite keys will be important and sensitive. To implement the composite key, using the key class can be helpful. In any case, regarding the key, two types of operations should be considered. The first operation compares the values, and the second is related to generating the next value. In the following code, you can see how to implement the key class:
public override bool Equals(object? obj)

{

if (obj is not CompoundKey)

return false;

CompoundKey other = (CompoundKey)obj;

if (keys.Length != other.keys.Length)

return false;

for (int i = 0; i < keys.Length; i++) if (!keys[i].Equals(other.keys[i])) return false; return true; } The preceding method shows the value comparison process for the composite key. As it is clear in the code, inside the for loop, the value of all the members of the composite key is compared one by one. According to the requirement, the preceding implementation may be varied and different to compare the values of the keys. Also, to make a composite key, you can proceed as follows: public class CompoundKey { private object[] keys; public CompoundKey(object[] keys) => this.keys = keys;

}

Different constructors with different input types can be defined to create composite keys with specified members or values, and the preceding code can be improved.

Keys can be on two levels in terms of uniqueness. The first level may require that the key value in a table is always unique, and the second level may require that the key value be unique in the entire database. It is usually very common to use the first level, while the advantage of the second level will be that an identity map will be needed.
Depending on the data type selected for the key, we may reach the maximum value when using it. In this case, to generate the next value, the value of the key used for the deleted records can be used. This method will be provided that the deletion has occurred physically. 64-bit data types or combination keys can reduce the possibility of reaching the maximum allowed value. Usually, the mistake is using data types such as GUID. Although the value of these data types is always unique, they occupy a lot of space. Therefore, before using them, the necessary checks must be done.
When creating a new object and storing its value in the database, generating a new value for the key will be necessary. Different ways to generate a new value for the key include setting the key column in the database to auto-generate, using the GUID for the key, or implementing your mechanism:
Using the auto-generate method is simpler. However, it may cause problems in the record and object mapping process. Because until the record is inserted in the database and then the same data is not retrieved from the database, the value assigned to the key will be unknown. An alternative method for auto generate in Microsoft SQL server database is to use sequence, which plays the role of database level counter.
Using the GUID method, as mentioned earlier, may cause performance problems.
To implement a custom mechanism, methods such as using the MAX function to find the last value of the key may also be used. This method may be useful for small tables with a lower simultaneous change rate. It will not be a good method for larger tables or tables with a high simultaneous change rate because, while having a negative effect on performance, it may be due to not having the appropriate isolation level for the transaction. Multiple objects get a common value for the key. Another way to implement this method is to use a key table. Using this table, you can store the next value for the key in this table and read the next value when you need to create a new object. It is better to access the key table through a separate transaction so that the rest of the transactions that need the key do not wait for the lock to be released. The problem with this method is that if the insert transaction is canceled for any reason, the generated key value will not be usable.
An alternative method for using the identity field design pattern is the identity map design pattern.
Consequences: Advantages

Using this design pattern, a common feature can be placed between the database records and the object created in the memory. The object and its corresponding record in the database can be identified through that feature. Without using this design pattern, in databases, the primary key value is usually used to identify the uniqueness of a record. In .NET, objects have their unique identifier, but there is no relationship between these two values.
Consequences: Disadvantages

If the data type of the key is a string, generating a new key or comparing the values of the keys can be time-consuming or heavy operations.
If the new value of the key is duplicated for any reason, if this value is not sent to the database, it cannot be noticed that it is duplicated. Obviously, in this case, performance will suffer.
Applicability:

When there is a need to map records in the database to objects, using this design pattern can be useful. This mapping usually uses domain models or row data gateway design patterns.
Related patterns:

Some of the following design patterns are not related to identity field design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Identity map
Domain model
Row data gateway
Foreign key mapping
Name:

Foreign key mapping

Classification:

Object-relational structures design patterns

Also known as:

---

Intent:

Using this design pattern, the foreign key relationship between tables in the database is mapped as a relationship between objects.

Motivation, Structure, Implementation, and Sample code:

Continuing the example presented in the identity field design pattern, we assume we need additional information for each book, located in a separate table next to the book object. The relationship between the books and the bookDetail is one-to-one. One-to-one relationship means that each book has only one record in the bookDetail table, and each record in the bookDetail table corresponds to one book. In other words, the following relationship can be imagined between the books and bookDetail tables:

Figure%209.1.png
Figure 9.1: Relationship between books and bookDetail tables

As it is clear from Figure 9.1, ER diagram, the books table has a primary key column called bookId, and the bookDetail table also has a foreign key column called bookId. In the bookDetail table, the bookId column also plays the role of the primary key. To implement this relationship, you can do as follows:

public class Book

{

public int BookId { get; set; }

public string Title { get; set; }

public DateOnly PublishDate { get; set; }

private BookDetail detail;

public BookDetail Detail

{

get => detail;

set

{

detail = value;

if (detail.Book == null)

detail.Book = this;//’this’ refers to the current instance of the class }

}

}

public class BookDetail

{

public int BookId { get; set; }

public byte[] File { get; set; }

public Book Book { get; set; }

}

As it is clear from the preceding code, each Book and BookDetail class has designed the identity field design pattern. The BookId property in these two classes is mapped to the bookId column in the books and bookDetail tables. Also, the Book class has a property called Detail related to the BookDetail class. The BookDetail class also has a property called Book and is related to the Book class. This relationship maps the database's foreign key to the relationship between objects. In the preceding code, a one-to-one relationship is implemented in a bidirectional way, which means that the corresponding BookDetail object can be accessed through a Book object and vice versa. Removing one of the Book or Detail properties is enough to implement this relationship unidirectionally.

In this one-to-one implementation, inserting, editing, and deleting will be simple operations. To insert or edit a record in the supplementary information table of the book, create the BookDetail object and send it to the database. The only important point in constructing this object is to comply with the principles related to the foreign key constraint.

Next, suppose that we need to implement the comments that the readers have given to the book. For this purpose, the comments table is designed in the database. There is a one-to-many relationship between the books and comments tables, which means that there can be several comments for one book, and on the other hand, each comment is only specific to one book. With these explanations, the ER diagram can be imagined as follows:

Figure%209.2.png
Figure 9.2: Relationship between books and comments tables

As shown in the Figure 9.2 ER diagram, the comments table has a column called bookId, which plays the role of a foreign key and is connected to the bookId column in the books table. Also, as determined by the relationship type, several records may be in the comments table for each bookId. With these explanations, the following code can be considered to implement this relationship:

public class Book

{

public int BookId { get; set; }

public string Title { get; set; }

public DateOnly PublishDate { get; set; }

private ICollection comments = new List();

public ICollection Comments

{

get => comments;

set

{

comments = value;

foreach (var item in comments.Where(x => x.Book == null))

item.Book = this; //’this’ refers to the current instance of the class

}

}

private BookDetail detail;

public BookDetail Detail

{

get => detail;

set

{

detail = value;

if (detail.Book == null)

detail.Book = this;

}

}

}

public class Comment

{

public int CommentId { get; set; }

public string Text { get; set; }

private Book book;

public Book Book

{

get => book;

set

{

book = value;

if (!book.Comments.Contains(this))

{

book.Comments.Add(this);

}

}

}

}

As it is clear in the preceding code, the Book class has an ICollection property called Comments, through which you can see all the comments related to that book. Also, the Comment class has a property called Book, which can connect the comment to its corresponding book. This method will also represent the implementation of one-to-many relationships. Remember that the preceding implementation is bidirectional, and it is possible to implement this relationship unidirectionally, as discussed in BookDetail.

In implementing the one-to-many relationship. There are three different ways to implement the editing process:

Delete and insert: This method is the easiest way to implement the editing operation. In this method, all the comments related to the book are removed from the comments table, and all the comments in the Book object are inserted into the comments table. This method has various problems, including that the comments related to the book may not have changed. In this method, we will have to delete and insert them again. Also, the bigger problem with this method is that this method can only be used if the comments have not been used elsewhere.
Reconciliation: Can be done in two ways:
Reconciliation with the data in the database: In this method, the comments related to the book should be read from the database and compared with the current comments. Any comment in the database but not in the list of current comments should be deleted. Any comment in the current list and not among the comments read from the database should be inserted.
Reconciliation with previously fetched data: In this method, storing the book comments fetched in the beginning will be necessary. This method will work better than the previous method because there is no need to refer to the database, but the data reliability will be low. The comparison method in this method will be the same as the previous method.
Using a bidirectional relationship instead of a unidirectional relationship: This method, also called the Back Pointer, allows us to access the related book through each comment instead of accessing the list of comments through the book. In this case, we will have a one-to-one relationship with the book for each comment, and as the one-to-one relationship explained earlier, the editing process can be implemented.
For the delete operation, the process will be almost similar. Deleting a comment often means that the comment is completely deleted, which naturally should also be deleted from the table. Or, it means that the comment has been transferred from one book to another, in which case, the comment record will also be edited when processing that book. In some cases, the comment may not be related to a specific book. In such cases, the value of the bookId column in the comments table will be equal to null (This method is only possible if the corresponding foreign key constraint allows storing null in the foreign key column table).

Notes:

If the relation is of immutable type, then the editing operation will be meaningless. For example, comments sent for a book cannot be edited or transferred to another book.
Using the UnitOfWork design pattern to implement editing, inserting, or deleting scenarios can be very helpful.
In implementing this design pattern, the occurrence of a loop is very likely. You can benefit from the lazy load or identity map design pattern to prevent stack overflow and properly manage the loop.
In the one-to-many relationship, you can use join to write a query to receive the list of book comments instead of sending several queries to the database. By sending one query to the database, all the information is required to be received. In this method, the number of remote calls or references to the database will be reduced, and the efficiency will be significantly improved.
Consequences: Advantages

Using this design pattern, the first principle of normalization can be observed.
Consequences: Disadvantages

Implementing a many-to-many relationship using this design pattern is impossible because, according to the first principle of normalization, the value of several foreign keys cannot be stored in one field.
Applicability:

To implement relationships between tables, this design pattern can be used. The association table mapping design pattern should be used to implement many-to-many relationships.
Related patterns:

Some of the following design patterns are not related to foreign key mapping design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Identity field
Unit of work
Lazy load
Identity map
Association table mapping
Association table mapping
Name:

Association table mapping

Classification:

Object-relational structures design patterns

Also known as:

---

Intent:

Using this design pattern, the many-to-many relationship between tables in the database is mapped as relationships between objects.

Motivation, Structure, Implementation, and Sample code:

In the continuation of the example presented for the foreign key mapping design pattern, let us assume that we need to have the information of the authors of the book along with the book object. The book information is in the tblBooks table, and the author information is in the authors table. Each author may have written several books and may have several authors. With these explanations, we can conclude that the relationship between the book and the author is many-to-many, and the foreign key mapping design model cannot implement this model. The foreign key mapping model is useful when one side of the relationship is a single value (such as one-to-one or one-to-many relationships). Now we are facing a relationship where both sides are multi-valued.

In the database world, there is a famous solution to solve this problem, and that is to use an intermediate table. The task of this intermediate table is to convert the many-to-many relationship into two one-to-many relationships. This intermediate table is sometimes called a link table. The name of this intermediate table in the given example is bookAuthors. The relationship between the tblBooks and the authors can be imagined as follows:

Figure%209.3.png
Figure 9.3: Relationship between tblBooks and authors tables

As shown in Figure 9.3, ER diagram, the tblBooks table has a one-to-many relationship with the bookAuthors table. Also, the authors table has the same one-to-many relationship as the bookAuthors table. The primary key in the preceding link table is a composite key, and the primary key is defined from the combination of the bookId and authorId columns. Now that the many-to-many relationship problem has been solved in the database, you can use the association table mapping design pattern to solve this problem at the model level and also fix the .NET codes:

public class Book

{

public int BookId { get; set; }

public string Title { get; set; }

public DateOnly PublishDate { get; set; }

public ICollection Authors { get; set; } = new List();

}

public class Author

{

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public ICollection Books { get; set; } = new List();

}

As it is clear in the preceding code, the Book class is connected to the Author class through the Authors property, and the Author class is also connected to the Book class through the Books property. The important thing about this design pattern is that, as seen in the preceding code, the link table designed in the database has no related objects.

Suppose we need to get the list of authors of a specific book. There are two ways to read the information in this structure:

Refer to the link table and get different authorIds for the desired book and then refer to the authors table and get the information of each author. If the data is not in the memory and it is necessary to refer to the database for each step, we will face a performance problem, and otherwise, if the data is in the memory, this process can be simple.
Using Join in the database and receiving information at once. In this case, going back and forth to the database will be reduced, which will reduce the number of remote calls and improve efficiency; on the other hand, complexity will be created to map the results on the models.
To perform the editing operation, considering that the records of the link table are often not related to another table, therefore, the editing operation can be easily implemented in the form of deletion and insertion operations (Contrary to what we saw in the foreign key mapping design pattern). If the records of the link table are related to other tables, then the same tasks as mentioned in the foreign key mapping design pattern should be performed to perform editing operations.

Notes:

Sometimes the link table, apart from maintaining the values of the foreign keys, also carries additional information. In this case, the link table will have its class. For example, suppose we need to know how much each author contributed to writing a book, and the contribution value is the data that should be stored in the bookAuthors table.
Consequences: Advantages

Using this design pattern, you can connect two different tables without adding a new column to the table and simply by creating an intermediate table.
Consequences: Disadvantages

This design pattern can be used to implement other types of communication, but it is not recommended due to the complexity of this task.
Applicability:

To implement many-to-many relationships, this design pattern can be used.
Related patterns:

Some of the following design patterns are not related to the association table mapping design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Foreign key mapping
Dependent mapping
Name:

Dependent mapping

Classification:

Object-relational structures design patterns

Also known as:

---

Intent:

Using this design pattern, a class becomes responsible for managing the communication of the child class with the database.

Motivation, Structure, Implementation, and Sample code:

Following the examples presented in the previous sections, suppose we need to have the contact information and address of the authors. The relationship between the contact information table or contactInfo and the authors table is one-to-many. In this way, each author can have several contact information. For example, one author can provide a phone number, mobile phone number, and work address in the form of contact information, and another author can provide a home address and work address.

To implement this scenario, it is very important to pay attention to one point: the identity and nature of the contact information, as the author is meaningful. In other words, the contact information cannot be accessed and worked by itself, and all transactions must be done through the author. This structure is exactly what the dependent mapping design pattern seeks to provide.

In this design pattern, we face a series of dependent elements and an owner for that dependent element. All transactions necessary for fetching, inserting, editing, and deleting the dependent element will happen only through its owner. Now, with the explanations provided, consider the following codes to implement the requirements:

public class Author

{

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public ICollection ContactInfo { get; set; }

public void UpdateAuthor(Author author)

{

this.FirstName = author.FirstName;

this.LastName = author.LastName;

UpdateContactInfo(author);

}

// Editing contact information includes deleting and re-inserting it

public void UpdateContactInfo(Author author)

{

RemoveAllContactInfo();

foreach (var item in author.ContactInfo)

{

AddContactInfo(item);

}

}

/*

All communication of contact information with the database

happens through the owner

*/

public void AddContactInfo(ContactInfo contactInfo)

=> ContactInfo.Add(contactInfo);

public void RemoveContactInfo(ContactInfo contactInfo)

=> ContactInfo.Remove(contactInfo);

public void RemoveAllContactInfo() => ContactInfo.Clear();

}

public class ContactInfo

{

public ContactType ContactType { get; set; }

public string Value { get; set; }

}

public enum ContactType : short

{

HomePhone,

MobilePhone,

HomeAddress,

WorkAddress

}

The preceding code shows that the Author class has a property called ContactInfo. This property maintains a set of contact information for the author. This class provides the necessary methods to insert and delete the author's contact information (according to the requirement, different implementations may be provided for these methods, and the code sample is provided here for simplicity). On the other hand, the ContactInfo class only has ContactType for the type of contact information (mobile phone number, home number, residential address, and work address) and Value to store the amount of contact information. As it is clear in the preceding code, acting through the Author class is necessary to communicate with the data related to ContactInfo in the database. Therefore, the Author class has the same role as the owner, and the ContactInfo class has the role of the dependent element. In implementing the UpdateContactInfo method in the Author class, the editing operation can be deleted and reinserted by using this design pattern and assuming the dependent element to be immutable.

Notes:

Each dependent element must have only one owner.
When using active record or row data gateway design patterns, the class related to the dependent element will not contain the necessary code to communicate with the database. As mentioned before, these communications should be established only through the owner, and these codes will be placed in the owner's mapper class when using the data mapper design pattern. Finally, there will be no dependent element when using the table data gateway design pattern.
Usually, when reading the owner's information from the database, related elements are also retrieved. If reading the information on dependent elements is costly, or the information related to dependent elements is of little use, the lazy load design pattern can be used to improve efficiency.
The dependent element does not have an identity field, so it is not possible to place it in the identity map; so, like other models, the method that returns a specific record by receiving the key value is meaningless for this element. All these works will be in the scope of the owner's duties.
A dependent element can be the owner of another dependent element. In this case, the main owner has the same duty towards the second dependent element as the first. Therefore, all the communications of the second dependent element with the database must happen through the owner of the first dependent element.
The foreign key of any table should not be inside the dependent element table unless the owner of both tables is the same.
Using UML, the relationship between the owner and the dependent element can be displayed through the Composition relationship.
The use of this design pattern requires two necessary conditions:
A dependent element must have only one owner.
No reference should be from any object other than the owner to the dependent element.
As much as possible, the communication graph using this design pattern should not be too long. The longer and larger this graph is, the more complicated the operation of managing the dependent element through the owner will be.
If the UnitofWork design pattern is used, it is not recommended to use this design pattern. Because UnitofWork does not recognize dependent elements, which can damage the principles of the dependent mapper design pattern.
Consequences: Advantages

If we consider the dependent element immutable, then the change process in the dependent element and how to manage it will be very simple. Editing can be simulated by removing all dependent elements and inserting them again.
Consequences: Disadvantages

Using this design pattern, it will be difficult to track changes. For example, whenever the dependent element changes, this change should be notified to the owner so that the owner can prepare and send the changes to be applied to the database.
Applicability:

This design pattern can be used when an object is available through another object. Often, the type of this relationship is one-to-many.
Using this design pattern will be useful when there is a one-to-many relationship between two models, but their connection is implemented unidirectionally.
Related patterns:

Some of the following design patterns are not related to dependent mapping design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Active record
Row data gateway
Data mapper
Table data gateway
Lazy load
Identity map
UnitofWork
Embedded value
Name:

Embedded value

Classification:

Object-relational structures design patterns

Also known as:

Aggregate mapping or composer

Intent:

Using this design pattern, one object can be mapped to several table columns related to another object.

Motivation, Structure, Implementation, and Sample code:

Following the previous examples, suppose we need to store the information of the publisher of the book. The book publisher has information such as name and address. The address of the book publisher has been designed and implemented as a value object in which the address consists of country, province, city, and street sections.

To implement this structure, one way is to consider two different classes for the publisher and their address, which have a one-to-one relationship with each other, and then map each of these classes to two different tables in the database and connect through the foreign key. If we do the design in this way, then the following structure can be considered for them:

Figure%209.4.png
Figure 9.4: Mapping Publisher and Address to separate tables

In Figure 9.4, two classes, Publisher and Address, are on the left side, each of which is mapped separately to the Publisher and PublisherAddress tables. The problem with this design is that we must use Join whenever we need to fetch and display the publisher's address next to their name. PublisherId also needs to be repeated in the Publisher and PublisherAddress classes, which leads to unnecessary duplicate data. The following code shows the sample query required to retrieve the name and address of the publisher with publisherId=1 using SQL:

SELECT p.Name, a.Country, a.Province, a.City, a.Street

FROM Publisher as p

INNER JOIN PublisherAddress as a

ON p.publisherId = a.publisherId

Where a.publisherId = 1

The preceding design and implementation have no terms of functionality, but the problem in the preceding design and implementation is performance. The embedded value design pattern helps to solve this performance problem by mapping one object to several columns of a table related to another object. In fact, by using this design pattern, the preceding model changes as follows:

Figure%209.5.png
Figure 9.5: Mapping Publisher and Address to one table

In Figure 9.5, two classes, Publisher and Address, are mapped to one table. Now, if we want to rewrite the previously presented query for this model, we will reach the following SQL code:

SELECT Name, Country, Province, City, Street

FROM Publisher WHERE PublisherId = 1

The preceding query will perform better than the previous query due to no need to use JOIN.

With these explanations, the proposed requirements can be implemented as follows:

public class Publisher

{

public int PublisherId { get; set; }

public string Name { get; set; }

public Address AddressInfo { get; set; }

public async Task FindAsync(int publisherId)

{

var reader = await new SqlCommand($"" +

$"SELECT * " +

$"FROM Publisher " +

$"WHERE ID = {publisherId}", DB.Connection).ExecuteReaderAsync();

reader.Read();

Publisher result = new()

{

PublisherId = (int)reader["Id"],

Name = (string)reader["Name"],

AddressInfo = new Address

{

Country = (string)reader["Country"],

Province = (string)reader["Province"],

City = (string)reader["City"],

Street = (string)reader["Street"]

}

};

return result;

}

}

public class Address

{

public string Country { get; set; }

public string Province { get; set; }

public string City { get; set; }

public string Street { get; set; }

}

As it is clear in the preceding code, the Address class does not have any method or code to communicate with the database, and all the communication of this class with the database happens through its owner, that is, the Publisher class. In other words, AddressInfo in the Publisher class is a value object. As it is evident in the Address class, this value object has no identity, and its identity finds its meaning through the owner. This is precisely why the Address class is not mapped to a separate table but only to a series of special columns of its owner object.

Notes:

Understanding when two objects can be stored in the embedded value format is a point that should be considered. The important point in making this decision is to pay attention to data retrieval and storage processes. The question must be answered whether the dependent element is useful outside the owner's domain. If yes, then should we probably store both in the same table (note, it is said probably because this decision is subject to other conditions as well), or should the dependent element data also be fetched whenever the owner data is fetched?
This design pattern is usually used for one-to-one relationships.
This design pattern is a special mode of the dependent mapping design pattern.
Consequences: Advantages

Using this design pattern, you can easily work with value objects.
Consequences: Disadvantages

This design pattern is only suitable for simple dependent elements. If the dependent elements are complex, this design pattern will not be suitable, and the serialized LOB design pattern should be used.
Applicability:

When we are faced with a table that has many columns, but in terms of modeling and object formation, we need to divide this table into different models and put methods specific to each class; in this case, this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to embedded value design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Dependent mapping
Serialized LOB
Serialized LOB
Name:

Serialized LOB

Classification:

Object-relational structures design patterns

Also known as:

---

Intent:

Using this design pattern, a graph of objects can be converted into a Large Object (LOB) and stored in a database field.

Motivation, Structure, Implementation, and Sample code:

In the continuation of the previous example for the embedded value design pattern, suppose a requirement is raised and, in its format, it is necessary to select and save the publisher's field of activity from thematic categories. The subject category has a tree structure as follows:

General
Novel
Science fiction
Motivational
Academic
Computer field
Software
Hardware
Industries
Law
International law
English language
Children
Teenagers
Adults
For example, for publisher X, we should be able to create and store the following tree:

Academic
Computer field
Software
English language
Children
Teenagers
Adults
There are different ways to implement this scenario, but one of the simplest ways is to convert each publication tree into a LOB and store the LOB result in a table field. In other words, the following code can be imagined to implement the above requirement:

public class Publisher

{

public int PublisherId { get; set; }

public string Name { get; set; }

public ICollection Categories { get; set; }

}

public class Category

{

public string Title { get; set; }

public ICollection SubSet { get; set; }

}

As you can see, the Publisher class has a property called Categories, where the LOB result is supposed to be stored. The structure of the Publisher table in the database is as follows:

CREATE TABLE Publisher(

PublisherId INT PRIMARY KEY IDENTITY(1,1),

Name NVARCHAR(200) NOT NULL,

Categories NVARCHAR(MAX)

)

As it is clear from the table structure, the Categories column in the Publisher table has a string data type, so the Categories property in the Publisher class must be somehow converted into a string. Although there was no requirement for this column to be a string, it could be binary. With this explanation, there are different methods to generate LOB, which are:

Binary LOB (BLOB): The object is converted into binary content and stored. The advantage of this method is the simplicity of production and storage and its small volume. The drawback of this method is that the result is not human-readable (of course, this may be an advantage in terms of security).
Character LOB (CLOB): The object is converted into a string and stored. The advantage of this method is that the result is human-readable, and it is also possible to run queries on them in the database without the need to convert them into objects again. The drawback of this method is that the content is bulky compared to the BLOB method.
After choosing a method to convert the object to LOB, the mechanisms for converting an object to LOB and LOB to an object must be implemented. We assume we want to use the CLOB method in the above example. Therefore, the following methods can be considered to insert or convert LOB into the expected object:

public async Task AddAsync(Publisher publisher)

{

return (await new SqlCommand($"" +

$"INSERT INTO Publisher " +

$"(Name, Categories) VALUES " +

$"N'{publisher.Name}',

N'{JsonConvert.SerializeObject(publisher.Categories)}'",

DB.Connection).ExecuteNonQueryAsync()) > 0;

}

public ICollection GetCategories(string serializedCategories)

=> JsonConvert.DeserializeObject>(serializedCategories);

As shown in the AddAsync method, when inserting publisher information, the information related to Categories is converted into a string with a JSON structure and then saved. Also, using the GetCategories method, the CLOB stored in the table is converted into the expected object. Newtonsoft Json.NET1 tool was used for these conversions.

Notes:

Sometimes it may be necessary to place the LOB in another table instead of the main table. This dependent table usually has two columns, one for ID and another for LOB.
In this design pattern, you should always be sure that the LOB content will be available only through its owner.
Suppose there is a separate database or data warehouse for reporting. In that case, the content of the LOB can be converted into a suitable table in the destination data source so that we do not face performance problems in reporting.
By using compression methods, it is possible to reduce the space consumption of CLOB. In this case, the effect of the most important drawback of this method is slightly reduced.
Consequences: Advantages

Converting the less used part of the object to LOB makes it possible to save the used space, improving efficiency.
Consequences: Disadvantages

When using this design pattern, if data outside the LOB has a reference inside the LOB, it will cause this design pattern to cause problems, including performance, for the program.
Some database management systems do not allow queries in XML or JSON formats; therefore, it is difficult to work with this data type on the database side. (Microsoft SQL Server allows writing queries on both XML and JSON).
Applicability:

When we are faced with an object where some of its data are not used much, then by using this design pattern, we can convert that part of the object into a LOB and store it in a field.
Related patterns:

Some of the following design patterns are not related to serialized LOB design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Embedded Value
Single table inheritance
Name:

Single table Inheritance

Classification:

Object-relational structures design patterns

Also known as:

Table per hierarchy or Discriminated mapping

Intent:

Using this design pattern, you can map the classes that participated in the inheritance structure to one table.

Motivation, Structure, Implementation, and Sample code:

Following the example in the previous sections, suppose that authors who work with publishers collaborate in two formats. Some authors work hourly, and others work with the publisher on a fixed monthly salary. Now a requirement has been raised, and we want to have the financial information of the authors as well. Specifically, the design that can be considered for this requirement can be seen in the form of the following class diagram:

Figure%209.6.png
Figure 9.6: Relation between the Author and his/her collaboration format

According to Figure 9.6 class diagram, authors are of two types. Authors who are paid monthly, whose monthly receipt information is in the MonthlyPaidAuthor class, and authors who are paid hourly and their hourly working information is in the HourlyPaidAuthor class. The Figure 9.6 class diagram can be implemented as follows:

public abstract class Author

{

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

}

public class HourlyPaidAuthor : Author

{

public int HourlyPaid { get; set; }

public int HoursWorked { get; set; }

}

public class MonthlyPaidAuthor : Author

{

public int Salary { get; set; }

}

As the preceding code shows, the HourlyPaidAuthor and MonthlyPaidAuthor classes inherit from the Author class. Now, this inheritance structure can be mapped to a table in the database so that the authors' data can be retrieved and worked with without using JOIN. Therefore, for the whole preceding structure, the following table can be considered:

CREATE TABLE Author(

AuthorId INT PRIMARY KEY IDENTITY(1,1),

FirstName NVARCHAR(200) NOT NULL,

LastName NVARCHAR(200) NOT NULL,

HourlyPaid INT,

HoursWorked INT,

Salary INT

)

As shown in the preceding T-SQL code, all the properties in the inheritance structure are presented as a table in the database. The important point is that there should be a way to distinguish between the records related to HourlyPaidAuthor and MonthlyPaidAuthor classes. To create this feature, we need to use a separate column. This column contains information about the corresponding class, such as the class name or a unique code for each class. This column is called Discriminator. Therefore, with these explanations, the structure of the table changes as follows:

CREATE TABLE Author(

AuthorId INT PRIMARY KEY IDENTITY(1,1),

FirstName NVARCHAR(200) NOT NULL,

LastName NVARCHAR(200) NOT NULL,

HourlyPaid INT,

HoursWorked INT,

Salary INT,

Type VARCHAR(100) NOT NULL

)

In the preceding table, the Type column plays the role of Discriminator. When the information of authors with monthly income is placed in this table, then the values of HourlyPaid and HoursWorked columns will be empty. The same rule is also true for records related to hourly paid authors, and for them, the value of the Salary column will be empty.

The following code shows how to retrieve records for each class:

public abstract class Author{

private readonly string discriminator;

protected Author(string discriminator) => this.discriminator = discriminator;

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

protected async Task GetAllAsync()

=> await new SqlCommand($"" +

$"SELECT * " +

$"FROM Authors " +

$"WHERE Discriminator=N'{discriminator}'",

DB.Connection).ExecuteReaderAsync();

}

public class HourlyPaidAuthor : Author{

public HourlyPaidAuthor() : base(nameof(HourlyPaidAuthor)) { }

public int HourlyPaid { get; set; }

public int HoursWorked { get; set; }

public async Task> ReadAllAsync(){

var result = new List();

var reader = await base.GetAllAsync();

while (reader.Read())

result.Add(new HourlyPaidAuthor

{

AuthorId = (int)reader["AuthorId"],

FirstName = (string)reader["FirstName"],

LastName = (string)reader["LastName"],

HourlyPaid = (int)reader["HourlyPaid"],

HoursWorked = (int)reader["HoursWorked"]

});

return result;

}

}

public class MonthlyPaidAuthor : Author{

public MonthlyPaidAuthor() : base(nameof(MonthlyPaidAuthor)) { }

public int Salary { get; set; }

public async Task> ReadAllAsync(){

var result = new List();

var reader = await base.GetAllAsync();

while (reader.Read())

result.Add(new MonthlyPaidAuthor

{

AuthorId = (int)reader["AuthorId"],

FirstName = (string)reader["FirstName"],

LastName = (string)reader["LastName"],

Salary = (int)reader["Salary"]

});

return result;

}

}

As specified in the GetAllAsync method in the Author class, the discriminator filter is applied when sending a request to retrieve records. To implement this section, you may take help from other design patterns, but here we have tried to display the code in the simplest possible way. Also, in the preceding code, the Author class is defined abstractly.

Notes:

Discriminator content can be either a class name or a unique code. If the class name is used, its advantage is the ease of use, and its drawback is the large space that the string content occupies in the database. Also, if the code is used, the advantage is that it takes up less space, and its drawback is the need for a converter to find out which class should be instantiated for each code.
When inserting or retrieving records, records related to a specific class can be accessed by providing the Discriminator value.
To simplify the implementation, the inheritance mapper design pattern can be used.
Consequences: Advantages

Using this design pattern, we only deal with one table in the database, simplifying the work in the database.
Since different data are in the same table, there is no need to use Join to retrieve and read records.
It is possible to refactor the code without changing the structure of the database. You can move various features in the inheritance tree without changing the database structure.
Consequences: Disadvantages

All the fields in this type of structure are not necessarily related to each other, and this can increase the complexity when using the table.
Using this design pattern, some columns in the table will have empty content, which can negatively impact performance and used space. Of course, many database management systems today provide different methods for compressing empty spaces.
In this case, we will face a very large and bulky table. Increasing the volume and size of the table means the presence of different indexes and the placement of multiple locks on the table, which can have a negative effect on performance.
Applicability:

When we face an inheritance structure and want to store the data related to the entire structure in a table, we can use this design pattern.
Related patterns:

Some of the following design patterns are not related to the Single Table Inheritance design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Inheritance mapper
Class table inheritance
Concrete table inheritance
Class table inheritance
Name:

Class table inheritance

Classification:

Object-relational structures design patterns

Also known as:

Root-leaf mapping

Intent:

Using this design pattern, in an inheritance structure, each class is mapped to a separate table in the database.

Motivation, Structure, Implementation, and Sample code:

Suppose that in the example provided in the single table inheritance section, we want to put the information of each class in its table. Suppose we need to store the author's general information in the authors table, financial information related to authors with hourly payments in the hourlyPaidAuthor table, and monthly payments in the monthlyPaidAuthor table.

With these explanations, the codes presented in the single table inheritance design pattern can be rewritten as follows:

public class Author

{

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public async Task GetAllAsync() => await new SqlCommand($"" +

$"SELECT * " +

$"FROM authors", DB.Connection).ExecuteReaderAsync();

}

public class HourlyPaidAuthor : Author

{

public int HourlyPaid { get; set; }

public int HoursWorked { get; set; }

public async Task GetAllAsync() => await new SqlCommand($"" +

$"SELECT * " +

$"FROM authors AS a " +

$"INNER JOIN hourlyPaidAuthor as h" +

$"ON a.AuthorId = h.AuthorId", DB.Connection).ExecuteReaderAsync();

}

public class MonthlyPaidAuthor : Author

{

public int Salary { get; set; }

public async Task GetAllAsync()

=> await new SqlCommand($"" +

$"SELECT * " +

$"FROM authors AS a " +

$"INNER JOIN monthlyPaidAuthor as m" +

$"ON a.AuthorId = m.AuthorId", DB.Connection).ExecuteReaderAsync();

}

As it is clear in the preceding code, the Author class has the GetAllAsync method. By using this method, the author's general information is fetched from the authors table. If we need to retrieve the information of hourly authors, we can use the GetAllAsync method available in the HourlyPaidAuthor class. In this class, Join is required and used to retrieve the author's information.

Notes:

There is no requirement to map the entire inheritance structure with one method, and you can benefit from different mapping patterns when dealing with an inheritance structure.
The important point for this design pattern will be how to read data from the database. For this, you can send a specific query for each table, which will cause performance problems. Another method would be to use Join to read the data. In using Join, the Outer Join method may also be used depending on the conditions.
Another point that should be paid attention to is how different records in different tables are related. There are different ways for this relationship. One of these ways is to use the common value in the primary key field. Another method is that each class has its primary key value and has the foreign key of the parent table.
To simplify the implementation, the Inheritance Mapper design pattern can be used.
Consequences: Advantages

By using this design pattern, there will be no waste of space in the database.
The connection between the domain model and the tables is clear.
Consequences: Disadvantages

To retrieve the data, it is necessary to read it using Join from several tables or send several queries.
Any refactoring that changes the location of properties in the inheritance structure will change the table structure.
Tables that are close to the root of the inheritance tree can cause problems due to frequent references.
Applicability:

When we face an inheritance structure, and we want to store the data related to each class in the inheritance structure in its table, we can use this design pattern.
Related patterns:

Some of the following design patterns are not related to the class table inheritance design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Single table inheritance
Inheritance mapper
Concrete table inheritance
Concrete table inheritance
Name:

Concrete table inheritance

Classification:

Object-relational structures design patterns

Also known as:

Leaf level inheritance

Intent:

Using this design pattern, each non-abstract class is mapped to a table in the database.

Motivation, Structure, Implementation, and Sample code:

In the example provided for the class table inheritance design pattern, suppose a requirement is raised so that we have two types of authors, the first type is paid hourly, and the second type is paid monthly. And we want to put the data of each type in its tables. Regardless of the type of author, each author has a series of common characteristics with other authors. Including first and last names, and so on. Because of this feature, usually, the following structure can be considered:

Figure%209.7.png
Figure 9.7: Relation between the Author and his/her collaboration format

As it is clear in the Figure 9.7 class diagram, the Author class is of an abstract type, and the two inherent classes MonthlyPaidAuthor and HourlyPaidAuthor are inherited from it. Using the concrete table inheritance design pattern, each intrinsic class is mapped to a table in the database. This means that in the database, we will encounter two tables monthlyPaidAuthor and hourlyPaidAuthor tables, each of which will contain columns related to themselves and their parent. Therefore, we will have the following tables in the database:

CREATE TABLE monthlyPaidAuthor(

AuthorId INT PRIMARY KEY IDENTITY(1,1),

FirstName NVARCHAR(200) NOT NULL,

LastName NVARCHAR(200) NOT NULL,

Salary INT

)

CREATE TABLE hourlyPaidAuthor(

AuthorId INT PRIMARY KEY IDENTITY(1,1),

FirstName NVARCHAR(200) NOT NULL,

LastName NVARCHAR(200) NOT NULL,

HourlyPaid INT,

HoursWorked INT

)

As can be seen, all the properties related to the parent class are repeated in each of the tables. With the preceding explanation, the following code can be imagined:

public abstract class Author

{

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public abstract Task GetAllAsync();

}

public class HourlyPaidAuthor : Author

{

public int HourlyPaid { get; set; }

public int HoursWorked { get; set; }

public override async Task GetAllAsync()

=> await new SqlCommand($"" +

$"SELECT * " +

$"FROM hourlyPaidAuthor", DB.Connection).ExecuteReaderAsync();

}

public class MonthlyPaidAuthor : Author

{

public int Salary { get; set; }

public override async Task GetAllAsync()

=> await new SqlCommand($"" +

$"SELECT * " +

$"FROM monthlyPaidAuthor", DB.Connection).ExecuteReaderAsync();

}

As it is clear in the preceding code, the Author class is defined abstractly and will not be mapped to any table in the database. The HourlyPaidAuthor class is inherited from the Author class to be mapped to the hourlyPaidAuthor table in the database. Also, this class has the implementation of the GetAllAsync method, and in this way, it reads the records related to its table. The implementation of insert, edit and delete methods will be the same.

Notes:

There is no requirement to map the entire inheritance structure with one method, and you can benefit from different mapping patterns when dealing with an inheritance structure.
To simplify the implementation, you can use the inheritance mapper design pattern.
Using this design pattern, you should consider the primary key's value. Usually, the primary key column is in an abstract class. If the code for generating the primary key is also in that class, duplicate values may be generated for the primary key.
Consequences: Advantages

Each table contains all its related data, and there is no need for additional columns.
There is no need to use Join to read the table data; all the parent class features are repeated in the child classes, resulting in their tables.
Consequences: Disadvantages

Primary key value management can be difficult.
In the case of refactoring the codes and changing the location of the properties, it will be necessary to change the structure of the tables as well. This change is less than required in the class table inheritance design pattern and more than in the single table inheritance design pattern.
If the parent class is changed, all tables related to child classes must be changed.
Applicability:

When faced with an inheritance structure and we want to store the data related to each class inherent in the inheritance structure in our table, we can use this design pattern.
Related patterns:

Some of the following design patterns are not related to concrete table inheritance design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Inheritance mapper
Class table inheritance
Single table inheritance
Inheritance mappers
Name:

Inheritance mappers

Classification:

Object-relational structures design patterns

Also known as:

---

Intent:

Organizing different existing mappers to manage the inheritance structure using this design pattern is possible.

Motivation, Structure, Implementation, and Sample code:

In the examples presented for different methods of mapping the inheritance structure to tables, it is often necessary to use a structure to prevent the generation of duplicate codes. There is also a need for a class to be responsible for loading and storing records related to a domain class. All these things can be done with the help of inheritance mappers' design patterns. Following the example provided for authors, suppose we want to implement fetch, insert, delete, and edit operations for each type of author. Consider the example of the concrete table inheritance design pattern. According to that example, we have an abstract class called Author and two intrinsic classes called HourlyPaidAuthor and MonthlyPaidAuthor.

Usually, we have a mapper for each model. The following class diagram can be considered for mappers:

Figure%209.8.png
Figure 9.8: Relation between AuthorMapper and different collaboration format mappers

As seen in the Figure 9.8 class diagram, the AuthorMapper class is abstract. We must use this model to implement fetching, inserting, editing, and deleting operations.

For retrieving records, since retrieving records from the database will result in the corresponding method returning one or a set of intrinsic objects. Therefore, according to the nature of this operation, the fetching operation must be placed in each of the inherent Mappers and cannot be placed in the parent’s abstract Mapper. Therefore, for fetching, the following codes for HourlyPaidAuthorMapper can be imagined:

public abstract class AuthorMapper { }

public class HourlyPaidAuthorMapper : AuthorMapper

{

protected HourlyPaidAuthor Load(IDataReader reader)

{

return new HourlyPaidAuthor()

{

AuthorId = (int)reader["AuthorId"],

FirstName = (string)reader["FirstName"],

LastName = (string)reader["LastName"],

HourlyPaid = (int)reader["HourlyPaid"],

HoursWorked = (int)reader["HoursWorked"]

};

}

public async Task> GetAllAsync()

{

var result = new List();

var reader = await new SqlCommand($"" +

$"SELECT * " +

$"FROM hourlyPaidAuthor", DB.Connection).ExecuteReaderAsync();

while (reader.Read())

result.Add(Load(reader));

return result;

}

}

The implementation of these methods for the monthlyPaidAuthorMapper class is also the same. The mechanism can be different from inserting or editing, and you can get help from the AuthorMapper class. For example, as follows:

public abstract class AuthorMapper

{

private readonly string tableName;

public AuthorMapper(string tableName) => this.tableName = tableName;

protected string GetUpdateStatementById(Author author)

{

return $"UPDATE {tableName} " +

$"SET FirstName =N'{author.FirstName}',

LastName=N'{author.LastName}' " +

$"#UpdateToken# " +

$"WHERE AuthorId = {author.AuthorId}";

}

protected async Task SaveAsync(string query)

=> await new SqlCommand(query, DB.Connection).ExecuteNonQueryAsync() > 0;

}

public class HourlyPaidAuthorMapper : AuthorMapper

{

public HourlyPaidAuthorMapper() : base("hourlyPaidAuthor") { }

public async Task UpdateAsync(HourlyPaidAuthor obj)

=> await SaveAsync(base.GetUpdateStatementById(obj)

.Replace("#UpdateToken#", $"HoursWorked = {obj.HoursWorked}"));

}

For the sake of simplicity, the codes related to data retrieval have been removed. As can be seen in the preceding code, the UpdateAsync method in the HourlyPaidAuthorMapper class performs editing operations with the help of the GetUpdateStatementById and SaveAsync methods defined in the parent mapper. The insertion mechanism is also similar to this mechanism. The deletion process is also like this mechanism, which can be seen in the following code:

public abstract class AuthorMapper

{

private readonly string tableName;

public AuthorMapper(string tableName) => this.tableName = tableName;

public string GetDeleteStatementById(int authorId)

=> $"DELETE FROM {tableName} WHERE AuthorId = {authorId}";

protected async Task SaveAsync(string query)

=> await new SqlCommand(query, DB.Connection).ExecuteNonQueryAsync() > 0;

public virtual async Task DeleteAsync(int authorId)

=> await SaveAsync(GetDeleteStatementById(authorId));

}

public class HourlyPaidAuthorMapper : AuthorMapper

{

public HourlyPaidAuthorMapper() : base("hourlyPaidAuthor") { }

}

The codes related to editing and data retrieval have been removed for simplicity. As seen in the preceding code, the AuthorMapper class has implemented data deletion logic, and child mappers can use these methods.

Notes:

This design pattern is usually used alongside single, class, or concrete table inheritance design patterns.
Consequences: Advantages

It removes duplicate codes and thus increases the maintainability of the code.
Consequences: Disadvantages

For small scenarios, it can increase the complexity.
Applicability:

This design pattern can be useful when a database mapping based on inheritance exists.
Related patterns:

Some of the following design patterns are not related to inheritance mappers' design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Single table inheritance
Class table inheritance
Concrete table inheritance
Conclusion
In this chapter, you got acquainted with various methods for data mapping and mapping the structure of classes and tables in databases. Also, in this chapter, you learned how to store and retrieve Value Objects with a proper table structure.

In the next chapter, you will learn about Object-relational metadata mapping patterns.

1 Newtonsoft Json.NET is a popular high-performance JSON framework for .NET

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

NET 7 Design Patterns In-Depth 8. Object-Relational Behaviors Design Patterns

Chapter 8
Object-Relational Behaviors Design Patterns
Introduction
To organize the object-relational behaviors, the design patterns of this category can be divided into the following three main sections:

Identity map: Tries to provide a method to fetch each record from the database only once.
Lazy load: Tries to load an object's data when needed.
Unit of work: Tries to send business transactions to the database as a single transaction.
The choice between these three design patterns depends on the level of logical complexity that we want to implement.

Structure
In this chapter, we will cover the following topics:

Object-relational behaviors design patterns
Unit of work
Identity map
Lazy load
Objectives
In this chapter, you will be introduced to object-relational behaviors design patterns and learn how to manage business transactions properly. You will also learn how to improve performance and efficiency by reducing references to the data source or when necessary.

Object-relational behaviors design patterns
Chapter 7, Data Source Architectural Patterns, tried to explain how different objects can be linked to tables in the database. In this chapter, we face another challenge: paying attention to behaviors. Behaviors mean how the data should be fetched from the database or how it should be stored in it. For example, suppose a lot of data is fetched from the database, and some have changed. It will be very important to answer the question of which of the data has changed or how to store the changes again in the database, provided that the data consistency is not disturbed.

Ensuring that the data is not read and changed by someone else is an issue related to concurrency management and design patterns. How to manage and apply changes is something that the unit of work design pattern can be useful for. When using the unit of work to improve efficiency, we need to ensure that the data that has been read will not be re-read. This problem can be solved with the identity map design pattern.

When the domain model is used, most models connect with other models. Reading a model will lead to retrieving all its relationships, again jeopardizing efficiency. To solve this problem, you can use the lazy load design pattern.

Unit of work
Name:

Unit of work

Classification:

Object-relational behaviors design patterns

Also known as:

---

Intent:

This design pattern tries to send business transactions to the database as a single transaction.

Motivation, Structure, Implementation, and Sample code:

Suppose we are implementing an infrastructure in which we want to manage the projects and people involved in an organization's project. The project and team member information of each project will be stored in separate tables in the database. There are different ways to implement this scenario. A simple way is to send each business request directly to the database and manage the transaction by placing and defining the transaction at the database level. This method has a problem, and sending a series of small requests to the database is necessary. In a system with high requests and user volume, these small requests will become the root of a big problem.

Another way is to manage these business requests at the application level and, after completing the business transaction process, send the entire business transaction to the database in the form of a single transaction. The unit of work design pattern can be useful in achieving this design.

Suppose we assume that we are facing only one model named project. The events that happen in this model are the events that lead to the creation of a new project, the deletion or editing of the existing project, and the reading of the project. With this hypothesis, to track changes at the application level, you can simply define several collections or arrays to track the changes and send them to the database at the right time.

There are different ways to implement the unit of work pattern. One of the most common methods is to combine the implementation of this design pattern with the repository design pattern. For example, suppose we have two repositories named UserRepository and BankAccountRepository. These two repositories have their own DbContext object and are connected to a common database. Figure 8.1 shows this relationship:

Figure%208.1.png
Figure 8.1: User and Bank account repositories with separated DbContexts

In this case, because UserRepository and BankAccountRepository have different objects from DbContext, their work is sent to the database in the form of two transactions (two units of work). These two repositories must use a shared DbContext object to solve this problem. Figure 8.2 shows this arrangement:

Figure%208.2.png
Figure 8.2: User and Bank account repositories with shared DbContext

As shown in Figure 8.2, the two repositories UserRepository and BankAccount Repository use a shared DbContext object so that the work of these two repositories can be sent to the database in the form of one transaction (one unit of work). As stated in the repository design pattern, the generic repository can also be used to implement this design pattern. Next, we will implement the unit of work design pattern with a generic repository.

According to the preceding explanations, the class diagram of this model in the presence of the generic repository model can be considered as follows:

Figure%208.3.png
Figure 8.3: UnitofWork with generic repository UML diagram

As can be seen in Figure 8.3 class diagram, the GenericRepository class is defined as generic and implements the IRepository interface. The UserRepository class can also inherit from the GenericRepository class and change some of its implementations (we have not implemented this part and assumed that the behaviors of the UserRepository are consistent with the GenericRepository). The UnitOfWork class implemented the IUnitOfWork interface and used the IRepository interface inside. This is because all access to repositories happens through the UnitOfWork object. Refer to the following code for this structure:

public interface IUnitOfWork

{

void Commit();

}

public class UnitOfWork : IUnitOfWork

{

private SampleDbContext _context = new();

private IRepository _userRepository;

private IRepository _bankAccountRepository;

public IRepository UserRepository

{

get

{

if (_userRepository == null)

_userRepository = new GenericRepository(_context);

return _userRepository;

}

}

public IRepository BankAccountRepository

{

get

{

if (_bankAccountRepository == null)

_bankAccountRepository =

new GenericRepository(_context);

return _bankAccountRepository;

}

}

public void Commit() => _context.SaveChanges();

}

As you can see in the preceding code, when creating a repository object, a DbContext object is sent to it, and this makes both repositories have the same DbContext object. Also, for the preceding code, the generic repository design pattern is implemented as follows:

public interface IRepository

{

TEntity Find(TKey id);

List GetAll();

void Add(TEntity user);

void Update(TEntity user);

void Delete(TKey id);

}

public class GenericRepository : IRepository

where TEntity : class

{

internal SampleDbContext _context;

internal DbSet _dbSet;

public GenericRepository(SampleDbContext context)

{

_context = context;

_dbSet = context.Set();

}

public virtual List GetAll() => _dbSet.ToList();

public virtual TEntity Find(TKey id) => _dbSet.Find(id);

public virtual void Add(TEntity entity) => _dbSet.Add(entity);

public virtual void Delete(TKey id) => _dbSet.Remove(_dbSet.Find(id));

public virtual void Update(TEntity entityToUpdate)

{

_context.Entry(entry).State = EntityState.Modified;

}

}

Remember that this implementation can be improved from the repository and unit of work model. It is presented in the simplest implementation mode just to facilitate content transfer. Now, to use this structure, you can proceed as follows:

IUnitOfWork unitOfWork = new UnitOfWork();

unitOfWork.UserRepository.Add(new User { Id = 1, Name = "Ahmad" });

unitOfWork.BankAccountRepository.Add(

new BankAccount { Id = 101, AccountNumber = 12345});

unitOfWork.Commit();

In the preceding code, a UnitOfWork object is created. A new user is added through the UserRepository, and a bank account is defined through the BankAccountRepository. Finally, the changes are sent to the database as one transaction.

Notes:

To track changes using this design pattern, it will be necessary to record the changes somewhere. There are two ways to do this:
Caller registration: The user must register the changes in the unit of work object to be applied in the database. The advantage of this method is its flexibility in sending changes to apply to the database. The problem with this method is that the user may forget to register the changes for any reason.
Object registration: In this method, the registration process is done through the methods in the target object. Usually, when performing the retrieval operation, the desired object is registered as a clean version. Then, as soon as a change occurs on this object, the Dirty version is formed. Despite having overhead, two versions of the object can significantly help detect changes.
This design pattern is usually used next to the repository design pattern.
The significant point is that the entity framework has implemented this change tracking process and unit of work design pattern in the SaveChanges method. Here the question arises whether this feature in the entity framework still needs to implement this design pattern in the systems or not. As with the repository design pattern, the answer to this question is also challenging, but implementing a unit of work in systems can be very useful.
Consequences: Advantages

Provides better control for transaction management.
Due to the reduction in the number of visits to the database, it provides better efficiency for batch operations.
The ability to maintain flexibility and testability of codes will be improved, and unit tests can be compiled and executed easily using mock mechanisms.
With the existence of a unit of work, there will be no need to use classes like DbContext and so on in the business layers. It will enable a loose coupling between the business layer and the framework used in the data access layer to easily make changes in the data access layer without changing the business layer (for example, entity framework can be replaced with another ORM).
Consequences: Disadvantages

For business logic and simple data access, often the use of this design pattern increases the complexity because today, the majority of ORMs offer the features presented in this design pattern. Using this design pattern will be useful when a new feature is added to them apart from the features provided by ORM.
Applicability:

This design pattern can be useful when there is a need to separate the communication of the business layer from the data layer and optimize the number of communication times with the database.
Using this design pattern in Domain-Driven Design (DDD) is very practical.
This design pattern will be useful when we face a series of requests and want to process them in one transaction.
Related patterns:

Some of the following design patterns are not related to the unit of work design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Repository
Identity map
Identity map
Name:

Identity map

Classification:

Object-relational behaviors design patterns

Also known as:

---

Intent:

This design pattern tries to provide a method to fetch each record from the database only once. For this purpose, the records fetched for the first time are kept in a mapping set to be retrieved from this set whenever necessary.

Motivation, Structure, Implementation, and Sample code:

For example, consider the following code:

User user1 = userService.GetByName("Vahid");

User user2 = userService.GetByName("Vahid");

In the preceding code, data is read twice from the database and placed in two objects. In this situation, making changes to user1 has nothing to do with user2. Even if we want to save both objects in the database, one of the objects will be written on the other object. With this method, there is a risk that will result in concurrency problems. The identity map design pattern tries to solve this problem.

The identity map design pattern stores the records that have been read once from the database in a set so that when that record is needed later, instead of retrieving it from the database, the record is returned from the set that formed the identity map. In this way, the overall efficiency of the application will be improved. To implement an identity map, you can have a mapping set for each table in the database. Also, when implementing an identity map, issues related to concurrency should be considered.

Among the applications of this design pattern, we can mention the ability to cache data. The following class diagram shows the identity map design pattern:

Figure%208.4.png
Figure 8.4: Identity Map design pattern UML diagram

The Figure 8.4 class diagram shows that the UserMap class has a set of mappings named _mappings. This class has methods to add, delete and read records from the mapping set. Suppose we assume that the UserMap class is available from somewhere like the repository before sending the request to read the record to the database. In that case, that record is first searched by the Get method in the UserMap mapping set. If the desired record is not found in this set, the request to the database is sent, and the received response is first recorded in the mapping set using the Add method, and then the response is returned to the requester. With these explanations, the Figure 8.4 class diagram can be implemented as follows:

public class UserDbSet

{

public static List Users = new()

{

new User() { Id = 1, Username = “Vahid”, Email = “vahid@gmail.com” },

new User() { Id = 2, Username = “Ali”, Email = “ali@yahoo.com” },

new User() { Id = 3, Username = “Reza”, Email = “reza@gmail.com” },

new User() { Id = 4, Username = “Maral”, Email = “maral@gmail.com” },

new User() { Id = 5, Username = “Hassan”, Email = “hassan@yahoo.com” }

};

}

public class User

{

public int Id { get; set; }

public string Username { get; set; }

public string Email { get; set; }

}

public class UserMap

{

private readonly Dictionary _mappings = new();

public void Add(User user)

{

if (!_mappings.ContainsKey(user.Id))

_mappings.Add(user.Id, user);

}

public User? Get(int id)

{

if (_mappings.ContainsKey(id))

return _mappings[id];

return null;

}

public void Remove(int key)

{

if (_mappings.ContainsKey(key))

_mappings.Remove(key);

}

}

public interface IUserRepository

{

User Get(int id);

}

public class UserRepository : IuserRepository

{

private readonly UserMap _usermap;

public UserRepository() => _usermap = new UserMap();

public User Get(int id)

{

var cachedUser = _usermap.Get(id);

if (cachedUser == null)

{

var user = UserDbSet.Users.FirstOrDefault(x => x.Id == id);

_usermap.Add(user);

return user;

}

return cachedUser;

}

}

As seen in the preceding code, when the Get method is executed in the UserRepository class, the mapping set in the UserMap is first checked through the Get method. If this record already exists in this mapping set, then the record is returned without referring to the database; otherwise, the record is read from the database and registered in the mapping set, and returned.

Notes:

Retrieving data from the database multiple times and placing it in several objects can damage its accuracy. The same multiple retrieving from an external source can affect performance.
There will be one mapping for each database table in a similar structure (database structure and model structure are the same).
Consider the following code:
var query1 = ctx.Users.Where(x => x.Name == “Vahid”);

var user1 = query1.FirstOrDefault();

var user2 = query1.FirstOrDefault();

var query2 = ctx.Users.Where(x => x.Id == 1);

var user3 = query2.FirstOrDefault();

Console.WriteLine(user1.GetHashCode());

Console.WriteLine(user2.GetHashCode());

Console.WriteLine(user3.GetHashCode());

By executing the preceding code, we realize that all three objects, user1, user2, and user3, are one object. This means that the entity framework does the same thing as the identity map behind the scenes and does not allow loading an object more than once.
A key will be needed to design the mapping set, so the record can be found later based on that key. The best option for the key is to choose the table’s primary key, although other combinations can also be used as keys.
This design pattern can also be implemented generically. In this case, the whole process can be implemented using one class. The key selection is important in implementing the generic method. Because, in this case, all sets of mappings must have a fixed formula for the key.
How and where to store the mapping set is also important in this design pattern. This collection should be stored in a way that is different for each session. For some read-only data that never change, the storage method and location will not matter, and those data can be shared between sessions.
Since concurrency management is important when using this design pattern, the optimistic offline lock and design patterns are widely used.
Usually, using this design pattern in the unit of work is a better design. Because the unit of work is the data entry and exit point, if there is no unit of work, the presence of this design pattern next to the registry design pattern can be useful.
There is no need to use this design pattern for immutable objects. The reason for this is also clear. When an object is immutable, its value will not change; when the value does not change, there is no need to worry about the anomalies caused by the change. Among the most important immutable objects are value objects.
Consequences: Advantages

This design pattern can be used to implement the cache mechanism and thus reduce the number of references to the database.
Consequences: Disadvantages

This design pattern can manage the collision event in one session but cannot do anything for the collision event between several sessions. To solve this problem, you must use optimistic offline and pessimistic offline locks.
Applicability:

This design pattern can be useful when data needs to be read only once from the source.
Related patterns:

Some of the following design patterns are not related to the identity map design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Unit of work
Optimistic offline lock
Pessimistic offline lock
Registry
Lazy load
Name:

Lazy load

Classification:

Object-relational behaviors design patterns

Also known as:

---

Intent:

This design pattern tries to load an object’s data when needed. In this case, while object initialization, no data is loaded, which can positively affect performance.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a requirement in which the customer’s information is returned along with his orders. But in some places of the program, only customer information is needed, and in other places, orders are needed. In this case, receiving customer orders for each request will not be pleasant and can harm efficiency. Another way is to fetch the customer’s information from the database in every request but not fetch his orders. Wherever orders are needed, then the orders are picked up. After retrieving orders, if new orders are needed, the same list of previous orders can be returned.

The preceding method implements lazy load using the lazy initialization method. There are other methods to implement this design pattern, such as virtual proxy, value holder, and ghost, which we will learn about later.

Lazy initialization method
In Figure 8.5, you can see the class diagram of the Lazy Initialization method for the lazy load design pattern:

Figure%208.5.png
Figure 8.5: Lazy Initialization UML diagram

As shown in Figure 8.5 diagram, CustomerService receives order information from ExternalSource only once and only when needed. According to Figure 8.5 class diagram, the following codes can be considered:

public class Order

{

public int Id { get; set; }

public double Price { get; set; }

public int CustId { get; set; }

}

public class Customer

{

private List _orders;

public int Id { get; set; }

public string Name { get; set; }

public List Orders

{

get

{

if (_orders == null)

{

Console.WriteLine($"Loading orders for customer: {this.Name}");

_orders = OrderDbSet.Orders

.Where(x => x.CustId == this.Id).ToList();

}

return _orders;

}

}

}

As it is clear in the preceding code, inside the Orders property, it is checked whether the list of orders has already been loaded. If it is not loaded, the list of orders will be loaded. If you prefer to Orders again, the list of orders will not be loaded from the beginning, and the same list as before will be referred to. It can also be seen in the preceding code that Orders have no value when the Customer information is fetched, and when it is needed, the values inside will be filled. To use this code, you can proceed as follows:

1. Customer customer = CustomerDbSet.Customers.FirstOrDefault(x => x.Id == 1);

2. List orders = customer.Orders;

3. List orders2 = customer.Orders;

In line 1, customer information is fetched. This information was not fetched in this line because Orders information was unnecessary.
In line 2, Orders are needed for the first time, so in this line, the list of customer orders will be fetched from the database.
In line 3, where Orders are needed again, fetching has not happened, and the same list of previous orders is returned.
NOTE: .NET has a class called Lazy, which you can easily delay object initialization until it is used. For example, consider the following code:

Lazy lazyCustomer = new Lazy();

According to the preceding code, no instance of Customer is created. To create n instance, the instance should be received through lazyCustomer.Value. Referencing lazyCustomer.Value again will return the same object as before. The Lazy class allows you to provide the initialization process in the form of a Lambda expression to the constructor of the Lazy class:

Lazy lazyCustomer = new Lazy(()=>

{

Customer obj = new Customer();

//do something more with obj

return obj;

});

Now, using the Lazy class, you can rewrite the Customer class as follows:

public class Customer

{

private Lazy> _orders;

public int Id { get; set; }

public string Name { get; set; }

public Customer()

{

_orders = new Lazy>(() =>

{

return OrderDbSet.Orders.Where(x => x.CustId == this.Id).ToList();

});

}

public List Orders => _orders.Value;

}

Virtual proxy method

In this method, the virtual proxy object has the same structure as the main object. The main object is created the first time the virtual proxy object is requested. Then, the originally created object is returned whenever a reference is made to the virtual proxy. This implementation method combines proxy design patterns and lazy initialization method:

public interface IService

{

public List Orders { get; }

public int Id { get; set; }

public string Name { get; set; }

}

public class Customer : IService

{

private List _orders;

public int Id { get; set; }

public string Name { get; set; }

public Customer() =>_orders = OrderDbSet.Orders

.Where(x => x.CustId == this.Id).ToList();

public List Orders => _orders;

}

public class CustomerProxy : IService

{

private IService _service;

private void InitIfNeeded()

{

if (_service == null)

_service = new Customer();

}

public List Orders

{

get

{

InitIfNeeded();

return _service.Orders;

}

}

public int Id { get; set; }

public string Name { get; set; }

}

Ghost method
In this method, the desired object is incompletely loaded, and only information related to the primary key or main information is loaded. Other information that has not been loaded will be loaded on the first reference to them. The first reference to data that has not been loaded will cause all data to be loaded.

public class Customer

{

private string _name;

private List _orders;

private bool _isOrdersLoaded;

private bool _isloaded;

public int Id { get; set; }

public string Name

{

get

{

if (!_isloaded)

Load();

return _name;

}

}

public List Orders

{

get

{

if (!_isOrdersLoaded)

LoadOrders();

return _orders;

}

}

private void Load()

{

var customer = CustomerDbSet.Customers

.FirstOrDefault(x => x.Id == this.Id);

this._name = customer.Name;

_isloaded = true;

}

private void LoadOrders()

{

_orders = OrderDbSet.Orders.Where(x => x.CustId == this.Id).ToList();

_isOrdersLoaded = true;

}

}

Value holder method
In this method, another object called the value holder is responsible for managing lazy loading, and this object is used in the main object. The problem with this method is that the user must be aware of the presence of the value holder:

public class Customer

{

private ValueHolder _valueHolder;

public int Id { get; set; }

public string Name { get; set; }

public Customer() => _valueHolder = new ValueHolder(this.Id);

public List Orders => _valueHolder.GetOrders();

class ValueHolder

{

private List _orders;

private readonly int _id;

public ValueHolder(int id) => _id = id;

public List GetOrders()

{

if (_orders == null)

_orders = OrderDbSet.Orders.Where(x => x.CustId == _id).ToList();

return _orders;

}

}

}

Notes:

Using this design pattern when producing web applications or websites is very important. For example, loading an image or iframe only when the page is scrolled enough can have a significant impact on the initial loading speed of the web page.
The opposite of lazy loading is the eager loading method. The eager method will load the required resources once the code is executed.
Consequences: Advantages

Because it is not necessary to set all the objects, it increases the efficiency.
Using this design pattern in web-based applications will reduce overall bandwidth consumption.
Consequences: Disadvantages

Using this design pattern can increase the complexity because we always need to check whether the desired object has been loaded or not, or basically whether the desired object needs to be loaded lazily or not.
Applicability:

This design pattern can be very effective in implementing the singleton design pattern.
IQueryable or IEnumerable types support a type of lazy load called deferred execution.
The lazy load can be used in entity Framework when we need to get data from the Join of several tables.
When the complete loading of the object is not needed or is expensive, using this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the lazy load design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Eager loading
Singleton
Proxy
Conclusion
In this chapter, you learned how to properly design and manage business transactions using the unit of work design pattern. You also learned how to prevent redundant references to data sources using an identity map design pattern. Finally, you learned how to use a lazy load design pattern to retrieve only the required data from the data source. Now that you are familiar with these design patterns in this chapter, you will be familiarized with object-relational structures design patterns in the next chapter.

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

NET 7 Design Patterns In-Depth 7. Data Source Architecture Design Patterns

Chapter 7
Data Source Architecture Design Patterns
Introduction
To organize the data source architecture, the patterns of this category can be divided into the following four main sections:

Active record: An object has the task of a gateway to a record from the database, and along with this task, it also implements the business logic related to the domain of that record.
Data mapper: A layer of mappers is placed between the domain and the database, and in this way, they eliminate the dependency on the database and the domain.
Row data gateway: An object acts as a gateway to a record in the data source, and there is a different object for each record in the data source.
Table data gateway: An object acts as a gateway to a database table or view, and communication with all the records of that table or view happens through this object.
The choice between these four design patterns depends on the level of logical complexity that we want to implement.

Structure
In this chapter, we will discuss the following topics:

Data source architecture design patterns
Table data gateway
Row data gateway
Active record
Data mapper
Objectives
In this chapter, you will learn to know data source architecture design patterns and how to simulate the data source structure in your programs using different design patterns. You will also learn how to facilitate the construction and management of various SQL queries. In this chapter, you will learn how to link the data source structure to the domain model using mapping mechanisms.

Data source architecture design patterns
The most important task of the data access layer is facilitating communication with different data sources so the program can do its work. Most of today's data sources are relational databases, and with the help of the SQL language, you can easily communicate with all types of data sources and exchange data. Along with all the advantages of SQL, there are still some problems related to data sources. One of these problems is sometimes the lack of sufficient knowledge of SQL among programmers, which leads to writing wrong or ineffective queries.

With the preceding explanation, putting the SQL-related codes in a separate layer from the domain seems useful. The best way to implement this type of data infrastructure is to implement it according to the structure of database tables. In this case, there will be one class for each table in the database, and these classes will form gateways.

There are also different ways to have a gateway. One of these ways is to have a gateway object for each table record. This method is what the row data gateway is trying to express. Another way is to have one gateway object for each table. This method is also what the table data gateway is trying to express. Usually, in the table data gateway method, the result of the query sent to the database is returned as a record set. The connection and coordination between the table data gateway and record set also help to use this design pattern in the table module.

When using the domain model, due to the existing complexities, we may want each domain model to have the task of loading and changing its related data. This is where active records can come in handy.

With the increase of complexity within the domain, the logic of the domain is broken and divided between several classes. This causes a mismatch between the domain model and the database. Because there is no feature like inheritance in the database, on the other hand, with increasing complexity, it is necessary to test the domain's logic without the need to connect with the database. All these points and problems make us go for an indirect communication method between the domain model and the data source. In this case, completely separating the domain model from the database would be a better method. However, an interface will be needed to map domain objects to database tables. The data mapper design pattern will be useful in this section.

Choosing between a data mapper and an active record is a choice that depends on the level of complexity. For simpler scenarios, the active record will be useful, and as the complexity increases, using a data mapper will be a better option.

Table data gateway
Name:

Table data gateway

Classification:

Data source architecture design patterns

Also known as:

---

Intent:

Using this design pattern, an object plays the role of a gateway to a database table or view, and communication with all the records of that table or view happens through this object.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement is raised, and we need to connect to the database and perform Create, Read, Update, Delete (CRUD) operations on the user table. For this purpose, functions such as adding a new user, changing the password, deleting a user, finding a specific user, and receiving a list of users should be implemented.

There are different ways to implement this requirement. One of the ways is to connect to the database using raw queries and do the desired work. Using this method, the placement of the queries is important. If these codes are placed among the business logic code of the application, they will create a significant problem. The problem is that programmers will need to write queries. Most programmers either do not have enough knowledge to write queries or if they do have enough knowledge, database administrators will have difficulty finding and improving the performance of these queries.

However, a better option is placing the queries outside the business logic location. Table data gateway design patterns can be important in achieving this goal. In this way, all the required queries are collected and defined in a separate business logic section:

public class UserTableDataGateway

{

private SqlConnection connection;

public UserTableDataGateway()

{

connection = new SqlConnection("...");

}

public async Task GetAllAsync()

{

return await new SqlCommand("" +

"SELECT * " +

"FROM Users", connection).ExecuteReaderAsync();

}

public async Task FindByUserNameAsync(string username)

{

return await new SqlCommand($"" +

$"SELECT * " +

$"FROM Users " +

$"WHERE UserName = N'{username}'", connection).ExecuteReaderAsync();

}

public async Task ChangePasswordAsync(

string username, string newPassword)

{

return (await new SqlCommand($"" +

$"UPDATE Users " +

$"SET [Password] = N'{newPassword}' " +

$"WHERE UserName = N'{username}'"

, connection).ExecuteNonQueryAsync()) > 0;

}

}

As seen in the preceding code, the GetAllAsync method is used to get the list of all users. To find a specific user based on username, the FindByUsernameAsync method is used, and the ChangePasswordAsync method is used to change the password. Other operations, such as inserting a new user and deleting a user, can be defined similarly. Another noteworthy point in the preceding implementation is that for the sake of simplicity, in the definition of queries, things like SQL Injection and opening and closing the connection to the database have not been paid attention to. Still, in the real environment, it will be necessary to do such things. In the business logic layer, you can use this gateway if you need to work with the Users table. Also, database administrators must only check the gateways to monitor and improve queries.

Notes:

By using this design pattern, database queries are encapsulated in the form of a series of methods.
To implement this design pattern, you can also benefit from a Data set. While using this method, most of the operations will be similar, and you can also benefit from a public gateway and send things like the table name in the form of input parameters.
In implementing this design pattern, there is no requirement to communicate with the database tables. According to the requirement, it may be necessary to communicate with the views. If a public gateway is used for communication, it should be noted that it is often impossible to change the table or sub-tables through views1. Therefore, defining a public gateway for tables and a public gateway for views will probably be necessary. Also, to hide the structure of the tables, it is possible to benefit from the stored procedures in the database and create a gateway to communicate with these stored procedures.
The important point in using this design pattern is how to return the results to the user. For this purpose, Data Transfer Object (DTO) or methods such as mapping can be useful. Using the mapping method is often not a good option, as we will not be able to notice the problem at the time of compilation, which will cause a bug to appear. In using the DTO method, it should be noted that a new object must be created, and this method can be considered only if the created DTO can be reused. The results can be returned in the domain object format if this design pattern is used along with the domain model pattern.
This design pattern, table module, and transaction script patterns can be very useful. For example, this design pattern returns the record set required for the table module, so it can perform its processing on the results.
Consequences: Advantages

All queries are placed in the database separately from the business logic, making it easier to make changes to the queries.
The implementation of this design pattern is very simple.
Mapping between the gateway and database tables or views will be simple. Each table or view in the database is mapped to a gateway.
Consequences: Disadvantages

Using this design pattern next to the domain model will not be appropriate, and it is usually better to use the domain model design pattern and the data mapper pattern.
As the complexity in the domain increases, this design pattern will become more complex and does not have good scalability.
Applicability:

It can be useful for implementing small applications. If the business logic of the domain is simple, this design pattern can be very useful.
This design pattern and the table module design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the table data gateway design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Table module
Transaction script
Domain model
Data mapper
Data transfer object
Row data gateway

Row data gateway

Name:

Row data gateway

Classification:

Data source architecture design patterns

Also known as:

---

Intent:

Using this design pattern, an object acts as a gateway to a record in the data source, and there is a different object for each record.

Motivation, Structure, Implementation, and Sample code:

Suppose we want to rewrite the example presented in the table data gateway design pattern with a row data gateway. In the table data gateway design pattern, we put all the queries related to the user's table in one class and connect to the database through that class. This section shows the difference between the row data gateway design pattern and the table data gateway pattern. In the row data gateway model, instead of a class having all the queries related to a table, we work through an object with a database record.

It is obvious that to find a record from the database and assign it to an object, and we will need a separate class. We name this class Finder. Finder is tasked to find and return a record from the table based on the provided condition. In other words, the finder tries to return a gateway for each record. The returned gateway has two main parts:

Properties: This section is the one-to-one mapping between the properties of the class and the columns of the table or data source. For example, ID, username, password, and so on.
Behaviors: This section is the queries that need to be executed on the data source. For example: insert, edit, delete, and so on.
In the following code, we have rewritten the scenario related to users with row data gateway:

public static class UserFinder

{

public static async Task FindAsync(string username)

{

var reader = await new SqlCommand($"" +

$"SELECT * " +

$"FROM Users " +

$"WHERE UserName = N'{username}'",DB.Connection).ExecuteReaderAsync();

reader.Read();

return UserRowDataGateway.Load(reader);

}

public static async Task> GetAllAsync()

{

var gatewayList = new List();

var reader = await new SqlCommand(

"SELECT * FROM Users",DB.Connection).ExecuteReaderAsync();

while (reader.Read())

{

gatewayList.Add(UserRowDataGateway.Load(reader));

}

return gatewayList;

}

}

public class UserRowDataGateway

{

public string UserName { get; set; }

public string Password { get; set; }

public static UserRowDataGateway Load(IDataReader reader)

{

return new UserRowDataGateway()

{

UserName = reader["Username"].ToString(),

Password = reader["Password"].ToString()

};

}

public async Task ChangePasswordAsync()

{

return (await new SqlCommand($"" +

$"UPDATE Users " +

$"SET [Password] = N'{this.Password}' " +

$"WHERE UserName = N'{this.UserName}'",

DB.Connection).ExecuteNonQueryAsync()) > 0;

}

}

In the preceding code, issues related to SQL injection or connection opening and closing are not mentioned, but in the real project, you must pay attention to these issues.

UserFinder is responsible for mapping a record to a gateway in the preceding code. It first retrieves the record from the database and then assigns the result to a specific object through the Load method.

Notes:

In using this design pattern, a finder class is often considered for each data source (table, view, and so on).
This design pattern is very similar to the active record design pattern. If the row data gateway contains domain business logic, this design pattern essentially becomes an active record and contains the business logic of the database.
Like the table data gateway design pattern, this design pattern can also communicate with a table, view, or stored procedure.
Consequences: Advantages

When mapping the columns of a record to class properties, type conversion may be required. Using this design pattern, this type of conversion happens only in one place (Load method).
Mapping between database objects and classes is simple, as each table has a gateway class. Depending on the type of implementation, a finder class will probably be needed for each table.
Consequences: Disadvantages

Using this design pattern, the maintenance cost sometimes increases because a series of clear codes must be written constantly.
Dependence on the database in using this design pattern is high because the object's properties are directly dependent on the table's columns.
The compatibility of this design pattern with the domain model design pattern is low, and the use of this design pattern along with the domain model design pattern will cause us to face three different data view methods. The first view is in the form of a domain model, the second is in the form of a gateway, and the third is in the form of a database. This is even though two different data views are needed, so using this design pattern alongside the domain model is not recommended. It is better to use other design patterns, such as active records.
Applicability:

Using this design pattern, the transaction scripts design pattern is a good combination.
This design pattern can be useful when the business logic of the domain is simple, and the probability of making changes to the data source is low. Type Safety is important when designing and coding.
Related patterns:

Some of the following design patterns are not related to the row data gateway design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Table data gateway
Active record
Transaction scripts
Domain model
Active record

Name:

Active record

Classification:

Data source architecture design patterns

Also known as:

---

Intent:

In this design pattern, an object has the task of a gateway to a database record. In addition to this task, it also implements the business logic related to the domain of that record.

Motivation, Structure, Implementation, and Sample code:

An object has a series of data and behaviors, and data often needs to be persistent, stored, and retrieved in a database. This operation is presented and implemented in the form of data access logic. Using an active record design pattern, object-related behaviors, which are the business logic of the domain, are placed next to the data access business logic.

Suppose we want to rewrite the example presented in the row data gateway design pattern section with an active record design pattern. As seen in the row data gateway section, we used the finder class to find the desired record and shape the desired object, and through the gateway class, we implemented requirements such as changing the password. Now, if we add business logic related to the domain to that structure, the designed structure will become an active record:

public class UserActiveRecord

{

public string UserName { get; set; }

public string Password { get; set; }

public static UserActiveRecord Load(IDataReader reader)

{

return new UserActiveRecord()

{

UserName = reader["Username"].ToString(),

Password = reader["Password"].ToString()

};

}

public bool IsPasswordValid()

=> Password.Length > 6 &&

(Password.Contains('@') || Password.Contains('#'));

public async Task ChangePasswordAsync()

{

if (IsPasswordValid())

{

return (await new SqlCommand($"" +

$"UPDATE Users " +

$"SET [Password] = N'{this.Password}' " +

$"WHERE UserName=N'{this.UserName}'",

DB.Connection).ExecuteNonQueryAsync())> 0;

}

else

throw new Exception("The password is not strong enough.");

}

public async Task IncreaseFailedLoginAttemptAsync()

{

return (await new SqlCommand($"" +

$"UPDATE Users " +

$"SET [FailedLoginAttempt] = [FailedLoginAttempt] + 1 " +

$"WHERE UserName = N'{this.UserName}'",

DB.Connection).ExecuteNonQueryAsync()) > 0;

}

public async Task ResetFailedLoginAttemptAsync()

{

return (await new SqlCommand($"" +

$"UPDATE Users " +

$"SET [FailedLoginAttempt] = 0 " +

$"WHERE UserName = N'{this.UserName}'",

DB.Connection).ExecuteNonQueryAsync()) > 0;

}

public async Task GetFailedLoginAttemptAsync()

{

return (int)await new SqlCommand($"" +

$"SELECT [FailedLoginAttempt] FROM Users " +

$"WHERE UserName = N'{this.UserName}'",

DB.Connection).ExecuteScalarAsync();

}

public async Task IsUserExistsAsync()

{

return ((int)await new SqlCommand($"" +

$"SELECT COUNT(*) FROM Users " +

$"WHERE [Password] = N'{this.Password}' AND " +

$"UserName = N'{this.UserName}'",

DB.Connection).ExecuteScalarAsync()) > 0;

}

public async Task LoginAsync()

{

var loginResult = await IsUserExistsAsync();

if (loginResult == false)

{

await IncreaseFailedLoginAttemptAsync();

if (await GetFailedLoginAttemptAsync() > 3)

throw new Exception("Your account has been locked.");

}

else

await ResetFailedLoginAttemptAsync();

return loginResult;

}

}

In the preceding implementation, for the sake of simplicity, things like SQL injection and opening and closing the connection to the database have not been considered in the definition of the queries. However, in real scenarios and production environments, it will be necessary to consider such things.

In the preceding code, as you can see, in addition to the data access logic such as ChangePasswordAsync, IncreaseFailedLoginAttemptAsync, ResetFailedLogin AttemptAsync, GetFailedLoginAttemptAsync or IsUserExistsAsync methods related to domain business logic such as IsPasswordValid and LoginAsync are also present. As it is clear in the preceding code, the design is completely similar to the design of the row data gateway design pattern, with the difference that the business logic of the domain is also included in this design.

Another point in this design is that the finder class is not included, and the design of this class will be the same as the design done in the row data gateway design pattern.

Notes:

Part of the domain logic may be written using the transaction script pattern in this design pattern, and data access logic or data-related codes may be implemented with Active Record.
The data structure presented in the active record must be the same as the data structure of the database.
In using this design pattern, it is possible to communicate with the view or stored procedures instead of communicating with the table.
This design pattern is very similar to the row data gateway.
Consequences: Advantages

Implementation and understanding of active records are very simple.
It is very compatible with transaction script design patterns.
Since the domain business logic related to a record is next to the data access logic, the design is more coherent.
Consequences: Disadvantages

Using this design pattern can be useful only when the active record object is the same as the structure of the tables in the database. This design pattern can be useful in structures of the same shape. Still, if the business logic becomes more complex and there is a need to use capabilities such as inheritance and so on in the code, this design pattern will not be useful.
Due to the high dependence of the database structure on the object structure, it will be difficult to perform refactor operations at the database or object level, and hence the scalability will decrease.
The presence of domain business logic, data access logic, and increasing design and code coherence reduces reusability. Data access logic can be placed in parent classes through inheritance to solve this problem.
Due to the high dependence of this design pattern on the database, it is difficult to write unit tests.
Due to data access logic next to domain logic, the Single Responsibility Principle (SRP) and Separation of Concern (SoC) are violated. For this reason, this design pattern is suitable for simple applications.
Applicability:

This design pattern can be useful for implementing simple domain business logic such as CRUD operations or almost complex logic in the case that the possibility of changing the data source is low.
Related patterns:

Some of the following design patterns are not related to active record design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Transaction script
Row data gateway
Data mapper
Name:

Data mapper

Classification:

Data source architecture design patterns

Also known as:

---

Intent:

Using this design pattern, a layer of mappers is placed between the domain and the database, and in this way, the dependency on the database and the domain is eliminated. The task of these mappers is to transfer data from the domain to the database and vice versa.

Motivation, Structure, Implementation, and Sample code:

Suppose we want to rewrite the example of users mentioned in active record design patterns. As we saw in the active record design pattern, the principle of SRP and SoC was violated due to the high dependency on the database and the domain layer. Using this design pattern will be very simple. It is enough that the object related to the domain is not involved in issues related to the database or data source, and the data source or database is also not involved in the business logic of the domain. This makes these two areas not have mutual effects. For example, a domain-related object may have collections, inheritance, and so on.

The same features in the domain object will help you later in implementing the complex business logic of the domain. This is while these capabilities do not exist in the database, so this difference in different capabilities will cause two completely different data structures to exist. One structure conforms to the domain implementation, and the other to the database design. The part in charge of harmonizing and translating these two completely different structures is the same as the mapper. Consider the following code:

public class UserModel

{

public string UserName { get; set; }

public string Password { get; set; }

}

public class UserMapper

{

public static async Task Create(UserModel newUser)

{

return (await new SqlCommand($"" +

$"INSERT INTO Users " +

$"(Username, [Password]) VALUES " +

$"N'{newUser.UserName}', N'{newUser.Password}'",

DB.Connection).ExecuteNonQueryAsync())>0;

}

}

public class UserDomain

{

public static bool IsPasswordValid(string password)

=> password.Length > 6 &&

(password.Contains('@') || password.Contains('#'));

public static async Task Register(string username, string password)

{

if (IsPasswordValid(password))

{

return await UserMapper.Create(

new UserModel {UserName=username,Password=password });

}

else

{

throw new Exception("The password is not strong enough.");

}

}

}

In the preceding implementation, for the sake of simplicity, things like SQL injection and opening and closing the connection to the database have not been considered in the definition of the queries. Still, it will be necessary to consider such things in the real environment.

As it is clear in the preceding code, the UserDomain class is responsible for implementing the business logic of the domain. This class has no information about the structure of the data source and the database. Even this class has no information about the existence of data sources. The UserDomain class prepares a UserModel object according to its needs and delivers it to the UserMapper. UserMapper is responsible for communicating between the domain and the data source. Therefore, upon receiving a UserModel object, it prepares and sends it to the database.

Notes:

According to this design pattern, the one-to-one relationship between the database structure and the domain model has been lost, and this relationship may not necessarily be one-to-one.
The preceding code example is a simple data mapper design pattern implementation. In more complex design and implementation, to perform insertion, editing, or even deletion operations, the mapper will need to know the previous and new state of the object so that it can choose the appropriate operation based on that. Also, in order to be able to make these changes in the form of a transaction, the UnitOfWork design pattern can be used.
Considering the internal connections of the domain models with each other, it is necessary to know to what level the data should be fetched and placed in the model. If we do not have this information, we may have to retrieve the entire data, which of course, will not be the right thing to do. You can use the lazy load design pattern to solve this problem and improve the amount of fetched data.
There will be two ways to create domain model objects inside the mapper. The first way is to use the parametric builder for the domain model. In this way, to create a domain model object, it will be necessary to send the required information to the class constructor. The second way is to use the constructor without parameters. In this way, first, the object is created, and then its various characteristics are set. The first way is better in terms of object quality guarantee. The problem with using the first method is the possibility of a cyclic dependency problem. In circular dependency, object A needs object B to be created, and object B needs object A to be created.
Suppose the domain model is simple, and the task of designing and developing the database is in the hands of the domain developer team. In that case, the development process can be improved with direct access to the database. For this purpose, an active record design pattern can be used instead of this design pattern.
In the first step of software development, there is no need to produce a complete mapper with all possible features. Sometimes using a pre-produced and ready-made mapper is better than implementing a new mapper.
Consequences: Advantages

When developing the domain, the data source or database can be completely ignored during design, development, and testing.
There is a loose coupling between the data source and the domain.
It has very high compatibility with the domain model design pattern, and along with this design pattern, it helps the reusability of the code.
Consequences: Disadvantages

For simple business logic, using this design pattern will not be cost-effective because it increases the complexity by adding a new layer (mapper).
Applicability:

Using this design pattern can be useful if we want to design and develop the data source model independently of the domain model.
This design pattern is often used together with the domain model design pattern.
This design pattern can be useful when the business logic of the domain is complex.
When the probability of change in the data source or domain layer is high, using this design pattern will be useful.
Related patterns:

Some of the following design patterns are not related to the data mapper design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Unit of work
Domain model
Active record
Lazy load
Conclusion
In this chapter, you got acquainted with different design patterns related to data source architecture. In this chapter, you also learned how to simulate the data source structure and map them to different objects.

In the next chapter, you will learn about object-relational behavior design patterns.

1 If the view contains a series of conditions or a series of triggers are used, data can also be changed through the view.

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

NET 7 Design Patterns In-Depth 6. Domain Logic Design Patterns

Chapter 6
Domain Logic Design Patterns
Introduction
To organize the domain logic, the patterns can be divided into the following four main sections:

Transaction script: This is the simplest way to store domain logic. It is a procedure that receives input from the display layer, processes it, and returns the response to the display layer. Processing in this model can include validation and calculations, storing a series of data in a database, and so on.
Domain model: We create a domain model and put the logic related to the domain in these models. The difference between this pattern and the transaction script design pattern is that a procedure is responsible for handling an action. Still, in the domain model, each model only processes the part of the request that is related to it.
Table module: This is very similar to the domain model, and the main difference is that in the domain model, each object represents a record in the database, but in the table module, there is one object in general, which has tasks related to all objects. It does the relevant things. This design pattern works with the record set pattern. Also, the table module can be considered as an intermediate layer between the transaction script and domain model.
Service layer: Tries to determine the boundaries of the system and the capabilities available to the user layer. In this way, business logic, transaction management, and so on are hidden from the user's view.
The choice between these four design patterns depends on the level of logical complexity that we want to implement.

Structure
In this chapter, we will cover the following topics:

Domain logic design patterns
Transaction script
Domain model
Table module
Service layer
Objectives
In this chapter, you will learn domain logic design patterns and how to improve code maintainability while increasing complexity using different design patterns. These design patterns will teach you to manage complex business logic appropriately and optimally. In this chapter, you will get to know the domain and learn how to manage business logic in different domains.

Domain logic design patterns
The domain model design pattern can be very useful for complex logic, and it is important to understand when logic is complex and when it is not. Understanding this point is challenging, but using domain experts, or more experienced people, can obtain a better approximation.

Thanks to the various features available in .NET, using the table module design pattern can be useful. However, maintaining this design pattern becomes difficult with increasing complexity, and the codes will become very complex over time.

There are very few good use cases for the transaction script design pattern where it can be argued that the pattern is appropriate. Still, it is possible to start the implementation with this design pattern. Over time, the design can be migrated to the domain by acquiring the appropriate knowledge about the domain model:

Figure%206.1.png
Figure 6.1: The Relation between Effort to Enhance and Complexity of Domain Logic

According to Figure 6.1, with the increase in complexity, the use of table modules and transaction script design patterns will lead to a great increase in maintenance complexity and improvement capability. As it is clear in Figure 6.1, the x and y axis in this diagram are two qualitative attributes. As long as these qualitative attributes are not converted into quantitative attributes and numbers, it is impossible to obtain the correct border from these design patterns easily.

Now that we have a general understanding of the preceding three design patterns, we need to know the fourth design pattern of this category. When the domain model or table module design pattern is used, the handling of a request is distributed among several classes. In this case, a single interface is needed so that the presentation layer can deliver its request to that interface. That interface is responsible for sending the request to different classes, and this interface is the service layer.

Using the service layer as a covering layer on the transaction script will not be very useful because, in the transaction script, we will not face the complexity of communication in the domain model or table module. The important point in using the service layer is understanding how much behavior can be included.

The simplest mode of this pattern is to use it as a forwarder that takes the request and, without doing anything, refers it to its lower layers. The most complicated case is when the business logic inside the transaction scripts is placed inside the service layer. According to the previous explanations, placing business logic inside the service layer is useless. Over time, it will lead to the complexity of maintenance and improvement.

Transaction script

Name:

Transaction script

Classification:

Domain logic design patterns

Also known as:

---

Intent:

This design pattern tries to organize the business logic with the help of a series of procedures. Each procedure handles a presentation layer request in this design pattern.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a need to implement the purchase process. To implement the purchase process, we have reached the following model during the analysis:

Figure%206.2.png
Figure 6.2: Sample shipment process

According to Figure 6.2, the entire purchase process is considered a transaction. Based on the transaction script design pattern, this whole transaction can be implemented as a procedure in which the presentation layer delivers the information related to the purchase to this procedure. Then this procedure processes the transaction and returns the result to the presentation layer. According to these explanations, Figure 6.3 class diagram can be imagined for this design pattern:

Figure%206.3.png
Figure 6.3: Transaction Script design pattern UML diagram

As shown in the Figure 6.3 class diagram, ESaleTS handles purchase requests. The Sale method in this class will implement the desired procedure for purchase. To understand this pattern more precisely, consider the following pseudo code:

public bool Sale(int productId, int productCount, int userId)

{

/*

* Inventory check.

var product = tblProduct.Find(productId);

if(product.Stockx.UserId==userId)

var totalPrice = productCount*product.UnitPrice;

if(userWallet.Balance < totalPrice){ throw new Exception("The balance of the wallet is not enough."); } * Deduct the purchase amount from the wallet balance. userWallet.Balance = userWallet.Balance - totalPrice; * Subtract the number of requested goods from the total number of available goods. product.Stock = product.Stock - productCount; * Registration of shipment request. DeliveryRequest request = new DeliveryRequest( item: product, count: productCount, user: userId); tblDeliveryRequest.Add(request); Save(); */ return true; } As can be seen in the preceding code, the entire purchase transaction is implemented in the form of a procedure in which the business rules are checked first. Then the balance of the wallet and the goods inventory are updated, and a request to send the shipment is finally registered. Notes: In implementing this design pattern, procedures are directly connected to the database. If there is a need for a wrapper between the procedure and the database, this wrapper should be brief enough and away from complexity. To implement each procedure we may want to implement each procedure in the form of a series of sub-procedures. It is better not to have a reference to the presentation layer in implementing the procedures. In this case, changes can be made easily. To implement procedures, several procedures can be placed inside a class, or each can be placed in its class using the command design pattern. Usually, using the domain model design pattern will be more appropriate in more complex businesses than the transaction script design pattern. Consequences: Advantages Since each request is executed separately, different procedures do not need to know how other procedures work. Consequences: Disadvantages As the business logic becomes more complex, the maintenance work will become very difficult, and we may face a significant amount of duplicate code. As business logic becomes more complex, writing unit tests will become difficult. Applicability: This design pattern can be useful for implementing simple applications. When the team's general knowledge of object-oriented concepts could be higher, this design pattern can reduce production time in small applications. Related patterns: Some of the following design patterns are not related to transaction script design patterns, but to implement this design pattern, checking the following design patterns will be useful: Command Domain model Domain model Name: Domain model Classification: Domain logic design patterns Also known as: --- Intent: Using this design pattern, it tries to model the elements involved in the domain in the form of a series of objects that include data and behavior. Motivation, Structure, Implementation, and Sample code: As seen in the transaction script design pattern, when the complexity of the business increases, the transaction script pattern will not be suitable, and its use will threaten maintainability and development. Now, when faced with the complexity of business logic, we can divide business logic into different domains and give each domain the task of defining and managing tasks related to that domain. In a way, we are now doing domain modeling. The result of this modeling will be classed with a series of features that can include data and a series of behaviors related to the domain. Since each domain has its own data and data structure, it is easy to map each domain to a table in the database. With the preceding explanations, the domain model design pattern tries to create a connected network of objects where each object manages a part of the business logic. The noteworthy point in this model is that usually, the structure of the domain model will be very similar to the structure of the database model. However, it will also have differences with the database model, among which the most important differences can be the existence of more complex relationships, multiple features, value, and use of inheritance mentioned. To design the domain model, the first step is to know the domain. The domain is the scope of business that we are trying to solve related issues. Suppose we face an internet sales system; we usually face domains such as customers, carts, or products. As mentioned, each of these domains has behaviors specific to that domain, and along with these behaviors, they also have a series of data structures similar to the database model. To clarify the issue, consider the following code: public class Customer { public int CustomerId { get; private set; } public string Name { get; private set; } public string MobileNumber { get; private set; } public Customer(int customerId, string name) { if (customerId <= 0) throw new ArgumentException("Customer ID is invalid."); CustomerId = customerId; if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Customer name is required."); Name = name; } public Customer(int customerId, string name, string mobileNumber) : this(customerId, name) { if (string.IsNullOrWhiteSpace(mobileNumber)) throw new ArgumentException("Mobile phone number is required."); if (mobileNumber.Length > 11 && mobileNumber.Length < 10) throw new ArgumentException("Mobile number must be 10 or 11 digits."); if (int.TryParse(mobileNumber, out _)) throw new ArgumentException("Mobile number must be numeric."); MobileNumber = mobileNumber; } public string GetMobileNumber() { if (string.IsNullOrWhiteSpace(MobileNumber)) return string.Empty; string maskedMobileNumber; if (MobileNumber.Length == 10) maskedMobileNumber = "0" + MobileNumber; else maskedMobileNumber = MobileNumber; maskedMobileNumber = string.Concat( maskedMobileNumber.AsSpan()[..4], "***", maskedMobileNumber.AsSpan(7, 4)); return maskedMobileNumber; } } The preceding code shows a very simple structure for the client domain. According to the definition of this domain, the customer has a customerID, name, and mobile number. As you can see, a private set is used to define these features, and this is because the value of these properties cannot be changed from the outside. Next, with the help of the two defined constructors, you can create new objects of the customer type. In this case, the advantage of using these constructors is that we are always sure that the created objects are valid. This means every object made must have a customer ID and name or a customer ID, name, and mobile number. It is also prevented from assigning invalid values to properties according to the validation built into the constructor. The customer class also has a behavior called GetMobileNumber. In this method, before returning the value of the mobile number, we have replaced the three middle digits with a star (*). Many different things can be done in the created domains, and the preceding code was just a simple example of a domain. Notes: It should always be noted that the domain models store business logic. Therefore, testing them easily without dependence on other layers should be possible. Sometimes the domain class becomes big, and we face behaviors specific to scenarios. In this case, two decisions can be made. You can separate the specific behaviors of an application from the main class and form a new domain model, or you can keep these behaviors in the main domain model class. By separating the behavior to a specific application, the size of the domain model class is reduced. On the other hand, we may continue to face code duplication and maintenance problems. In this case, keeping the codes specific to a specific application in the domain model within the same domain model is suggested. Of course, this problem can be managed with the help of other concepts, such as aggregate roots, or by using different design patterns, such as strategy. Defining domains, they can be defined as simple or complex. For example, in a simple scenario, we may define a domain model for each table in the database. Or it is possible to define and connect the domains through a series of complex relationships with the help of different design patterns and different capabilities of object orientation, such as inheritance. Usually, for complex businesses, it is more useful to use complex domain models. Still, it should be kept in mind that, in this case, mapping between the domain and database models will be more challenging than simple domain models. Usually, to establish independence between the domain model and the database and prevent dependency, simple domain models are related to the active record pattern, and complex domain models are related to the data mapper design pattern. Consequences: Advantages All the logic related to a certain area of business is placed in one place, and in this way, repetition is prevented. Consequences: Disadvantages If we are faced with a team that does not have a proper attitude towards object-oriented or domain-oriented design, usually the use of this design pattern in this team will face difficulties. Applicability: This design pattern can be useful in implementing large and complex businesses. If we deal with a system where the business rules are undergoing fundamental changes, this design pattern can be useful. Still, if we are dealing with a small business with limited business rule changes, the transaction script design pattern will be more useful. Related patterns: Some of the following design patterns are not related to domain model design pattern, but to implement this design pattern, checking the following design patterns will be useful: Strategy Active record Data mapper Transaction Script Table module Name: Table module Classification: Domain logic design patterns Also known as: --- Intent: This design pattern uses a specific object to implement the business logic associated with all the records in a database table or view. Motivation, Structure, Implementation, and Sample code: Suppose there is a need to read various employee information from the database and provide it to the user. To implement this requirement, you can benefit from the domain model and implement the requirement. But the point of this method is that there will be one object for each of the personnel in this design pattern. This procedure is normal and coincides with the object-oriented design. Each object has an identity in this design, and the data and related behaviors are next to it. According to the above design, the question that arises is that if there was no need to use databases and benefit from their power and facilities, what would be the need to use relational databases or the presence of such a complex system? Wouldn't it be better and more appropriate to use the facilities of different parts in a useful way in order to implement our requirements? There are no precise and clear answers to these questions, and these are only questions that should be answered when designing and choosing a method from a group of methods. Another way to implement the preceding method is to use an object to manage the business logic of all the records of a table or view in the database. In the domain model method, one object is considered for each record, but in the design pattern, one is considered for all records. According to the preceding description, the following codes can be considered to implement the requirements raised using the table module design pattern: public class DbManager { protected DataTable dt; protected DbManager(DataSet ds, string tableName) => dt = ds.Tables[tableName];

}

public class RollCall : DbManager

{

public RollCall(DataSet ds) : base(ds, "rollcalls") { }

public double GetWorkingHoursSummary(int employeeId)

{

if (employeeId == 1) return 100;

else throw new ArgumentException("Employee not found.");

}

}

public class Employee : DbManager

{

public Employee(DataSet ds) : base(ds, "employees") { }

public DataRow this[int employeeId] => dt.Select($"Id = {employeeId}")[0];

public double CalculateWorkingHours(int employeeId)

{

var employee = this[employeeId];

var workingHours = new RollCall(dt.DataSet)

.GetWorkingHoursSummary(employeeId);

if (employee["Position"] == "CEO")

workingHours *= 1.2;

return workingHours;

}

}

To implement this design pattern, DataSet has been used. Since finding the table using DataSet is a fixed method for all tables (dataset.Table[abc]), the DbManager class is considered to implement this behavior. Other classes inherit from this class and prepare their own DataTable by providing the table name.

The Employee class is responsible for implementing the business logic related to all employee records. To find specific personnel, in this implementation, a C#.NET language Indexer is used. However, this behavior can be implemented using other methods as well. The CalculateWorkingHours method also calculates the number of staff working hours. To calculate the number of working hours of personnel, by providing employeeId, working hours can be calculated. The important point in implementing this method is to connect the method to the RollCall class. To create an object from the RollCall class, it will only be necessary to present the dataset to the constructor of this class, and in this way, the data table will be created.

Notes:

The object produced using this design pattern is very similar to conventional objects, with the difference that the object produced using this method has no identity. This means that if we want to get more details of a database record, we will need to search for that record from the mass of records using a parameter. Often, the primary key in the database can be a suitable parameter to find a record among the mass of records. For example, the following code shows how we can fetch employee detail by using employeeId:
Employee GetDetail(int employeeId)

To choose between the power of the domain model in implementing complex business logic and the ease of integration of the table module with the tabular structure, a cost-benefit analysis will be required.
There is often the need to communicate with several table modules to implement business logic.
To implement a table module, it can be defined as static or instance. In this case, you should consider the differences between class definitions as instance and static. One of the most important differences between these two methods of class definition is the possibility or impossibility of using inheritance.
In implementing table module queries, you can use the factory method design pattern with a table data gateway. The advantage of using a table data gateway is that you can connect to several data sources using the corresponding gateway using a table module. Each table module does its work on the record set. To access the record set, it will be necessary first to make the set of records available with the help of a table data gateway or factory method.
Consequences: Advantages

By using this design pattern, while collecting data and related behaviors in a class, you can benefit from the capabilities and advantages of the database.
Consequences: Disadvantages

If we face a more complex business logic since the relationships between objects are not seen in this design pattern and polymorphism features cannot be used. The domain model design pattern will be a better choice.
Applicability:

This design pattern can be useful when faced with a tabular data structure and looking for a convenient way to access the data.
Related patterns:

Some of the following design patterns are not related to the table module design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Domain model
Factory method
Table data gateway
Record set
Service layer
Name:

Service layer

Classification:

Domain logic design patterns

Also known as:

---

Intent:

This design pattern tries to determine the boundaries of the system and the capabilities available to the client layer. In this way, business logic, transaction management, and so on are hidden from the client's view.

Motivation, Structure, Implementation, and Sample code:

Suppose that several repositories are prepared using the repository design pattern. The client, as a controller, needs to communicate with one of these repositories and insert a new user. Before adding a new user, a series of business validations need to happen. Suppose that we want to check the non-duplication of the username in the form of this validation. There are different ways to implement this validation.

One way is to do this validation inside the controller. The problem with this way is that the controller will depend on the data access layer. For this purpose, we must validate the controller using a repository-type object.

The second way is to validate the repository. This method also has an important drawback: the Separation of Concern (SoC) will be violated. Because of this, we have entered the business logic into the data access layer.

The better way is to do the desired business logic as a middle layer. This middle layer takes the request from the controller and communicates with the repository, and performs the validation work. This middle interface is the service layer:

Figure%206.4.png
Figure 6.4: Service Layer design pattern UML diagram

As shown in Figure 6.4 diagram, UserService implements the IUserService interface, and the client submits a new user insertion request. Now the business logic is checked in the UserService, and a new user is inserted, or an appropriate error is returned to the client. The following code can be considered for this class diagram:

public interface IRepository

{

void Insert(User user);

User FindByUserName(string username);

}

public class UserRepository : IRepository

{

public User FindByUserName(string username)

=> UserDbSet.Users.FirstOrDefault(x => x.Username == username);

public void Insert(User user) => UserDbSet.Users.Add(user);

}

public interface IUserService

{

void Add(User user);

}

public class UserService : IUserService

{

private readonly IRepository _repository;

public UserService() => _repository = new UserRepository();

protected bool IsValid(User user)

=> _repository.FindByUserName(user.Username) == null;

public void Add(User user)

{

if (IsValid(user))

_repository.Insert(user);

else

throw new Exception("User exists!");

}

}

The preceding code shows that the business logic required to insert a new user happens before the insert operation is performed inside the UserService. In this way, the client does not need to communicate with the repository, and the UserService provides a new interface for the client to send requests through. In this provided interface, the client submits the insertion request, the UserService, performs the necessary checks, and if the request is valid and can be performed, it handles the request. To use the preceding code, you can do the following:

IUserService userService = new UserService();

userService.Add(new User { Username = "user5", Password = "123456" });

In the preceding implementation, one important point is that the client is accessing an object of type User. Usually, the repository's model is different from the model the client works with. This is because the repository usually works with a model that matches the data source, but the client works with a model that matches its needs. Therefore, it may be necessary to map between the model sent by the client and the model delivered to the repository in the implementation of the service layer.

Notes:

In examining the service layer design pattern, it is very important to pay attention to different types of business logic. In general, there are two types of business logic:
Business logic is related to the domain, called domain logic, in which we implement a solution for a problem within the domain. For example, we will implement the withdrawal of funds or how to increase the balance.
The business logic of the user program, which is called application logic, and in which the withdrawal process is implemented. For example, to withdraw money, first, the user's request is received, then the balance should be checked, then the current balance should be updated, and finally, a response should be returned to the user. How the account balance is checked or how the current balance is updated is addressed in domain logic format.
One of the most important applications of the service layer is domain-driven design. There is a need to establish a connection between several domains, and a series of checks take place. This way of implementing a service layer is also called a domain facade.
What methods or behaviors should be presented to the user through the service layer? The answer to this question is simple. The service layer should provide the user with the capabilities that the user needs. In other words, the capabilities provided by the service layer are based on user requirements. The user and their needs may directly relate to the User Interface (UI). This dependency is because the UI is usually designed and implemented based on the user's needs. In the following, it should be noted that most requirements in implementing a user program are limited to Create, Read, Update, and Delete (CRUD) operations. Implementing each part of CRUD usually includes a series of different tasks. For example, creation often requires a series of validations and then insertion. After the completion of creation, it is usually necessary to inform others or other parts of the program about the result of this operation so that those parts can do other necessary work. This part of the work, which includes informing other departments and coordinating with them, is one of the duties of the service layer.
To implement the service layer, the domain facade, and the operation script methods can be used:
In the domain facade implementation, the service layer is defined as a series of classes or thin interfaces, and the classes that implement these interfaces do not implement any business logic (the reason for using the word thin is the same), and business logic is implemented only through the domain model. These interfaces define the boundary and range the user layer can communicate with.
In implementing the operation script method, unlike the domain facade method, the service layer is defined as a series of fatter classes containing business logic (the reason for using the word fat is the same). Each class that implements business logic is usually called an application service in this type of implementation.
To implement the business logic of the domain, both the domain model and the transaction script design pattern can be used. It is recommended to use a domain model because the business logic is not repeated in different departments, and we will not face duplication.
To implement the service layer, it is better to implement it locally first, and then according to the need, if necessary, add the possibility of remote to it. To add remote functionality, you may need to use a remote facade.
Consequences: Advantages

Using the service layer, it is possible to get the business logic of domain-related business inside the domain, and the business logic of the user program will be implemented in another part. This separation will make the different business logic into different parts depending on their type (Domain logic or application logic).
Consequences: Disadvantages

If we face a single user and do not need to communicate with different transaction resources to meet their needs, this design pattern will increase the complexity.
One of the points mentioned in the implementation of this service is its remote implementation. Suppose there is no good cost-benefit analysis between the remote and local implementation of this design pattern. In that case, implementation will lead to an increase in complexity without achieving a specific advantage.
Applicability:

When faced with several different users and the need to communicate with multiple transaction sources for each user, then using this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the service layer design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Repository
Remote facade
Domain model
Transaction script
Conclusion
In this chapter, you got acquainted with the design patterns related to domain business and learned how to manage different businesses suitably using different design patterns. Always try to establish a good relationship between the complexity of the business and the upcoming requirements and choose the more appropriate design pattern. Otherwise, you cannot always use a fixed design pattern for different business management blindly and without careful examination. This will reduce the quality of the code, and over time, code maintenance will become a very serious challenge. In the next chapter, you will learn about design patterns related to data source architecture.

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

NET 7 Design Patterns In-Depth 5. Behavioral Design Patterns – Part II

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:

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 Answers { get; set; }

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 _mementos = new();

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 _mementos = new();

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 _subscribers;

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 mappings = new();

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 Items { get; set; } = new 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 Items { get; set; }

= 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

NET 7 Design Patterns In-Depth 4. Behavioral Design Patterns – Part I

Chapter 4
Behavioral Design Patterns – Part I
Introduction
Behavioral design patterns deal with the behavior of objects and classes. Different methods and algorithms for doing the same work between different objects are the main goals and focal points of this category of design patterns. In this category of design patterns, not only objects and classes are discussed, but the relationship between them is also discussed.

Structure
In this chapter, we will cover the following topics:

Chain of responsibility
Command
Mediator
State
Strategy
Template method
Objectives
It is expected that by the end of this chapter, you will be familiar with the six most famous behavioral design patterns and be able to understand their differences. Using the points presented in this chapter, it is still expected that you will be able to design the correct infrastructure for the classes to fulfil their expected behaviors.

Behavioral design patterns
In this category of design patterns, there are a total of eleven different design patterns, and in this chapter, the six most famous design patterns of this category are introduced, which are:

Chain of responsibility: It allows sending requests along a chain of objects.
Command: Encapsulate a request or task in the form of an object.
Mediator: Facilitate the communication between several objects by directing toward a central object.
State: Change the behavior of the object by changing its internal state.
Strategy: Separate different methods and algorithms for the execution of a task by forming an algorithmic family.
Template method: While defining the main body of the execution of a task, the steps of its execution are assigned to the child classes.
Behavioral design patterns are divided into two types: class and object. Class categories try to distribute a behavior between classes using inheritance. Template Method and Interpreter (Which will discuss in Chapter 5, Behavioral Design Patterns, Part II) are included in this category. On the other hand, the object category aims to show how objects can work together to do a task by using composition. Other behavior patterns are also included in this category.

Apart from the eleven GoF behavioral design patterns, other design patterns deal with the behavior between objects and classes. Including:

Blackboard pattern: Provides the possibility of integrating specialized and diverse modules for which there is no definite strategy for their development.
Null object pattern: Provide the default object value.
Protocol stack: Provide communication by different layers, like the different layers of the network.
Scheduled task pattern: Provide the possibility of executing a task in certain time frames.
Single-serving visitor pattern: It is an improved version of the Visitor design pattern.
Specification pattern: By using Boolean logic, it tries to combine different business logic.
Chain of responsibility
Name:

Chain of responsibility

Classification:

Behavioral design patterns

Also known as:

---

Intent:

This design pattern tries to make the incoming request move along a chain of processors, and at each step, each member of the chain checks the request and decides to deliver the request to the next member of the chain or terminate the request.

Motivation, Structure, Implementation, and Sample code:

Suppose that a requirement is raised and it is requested to design and implement an infrastructure through which users can create a web service. In general, the mechanism is the request that is created by the user and sent to the server, and the corresponding web service is found and executed on the server side.

After some time, it is announced that requests must be authenticated before running the web service. After a while, it is announced that access must be controlled for the requests after authentication. These continuous changes in the requirements gradually cause the quality of the code to drop.

To avoid this, the whole process can be seen as a chain, where each member of this chain performs a task. For example, the first node performs authentication, and the second node performs access control. If we want to add the request log mechanism later, we can easily define a node in the chain for it and then put it in place. Refer to Figure 4.1:

Figure%204.1.png
Figure 4.1: Calling web service request processing chain

With these explanations, it can be concluded that we are finally facing a series of handlers, and each handler is doing its work. Each handler has several tasks, including executing its logic and pointing to the next node. With these explanations, we are faced with the following class diagram:

Figure%204.2.png
Figure 4.2: Chain of Responsibility design pattern UML diagram

In Figure 4.2, the Handle method is responsible for implementing the logic of each node. This method should call the Next method to maintain the execution sequence. The ContinueWith method specifies the next node in the chain and stores its value in _successor, and the Next method executes the next nodes in the chain. For the diagram in Figure 4.2, we have the following code:

public class Request

{

public string IP { get; set; }

public string Url { get; set; }

public string Username { get; set; }

}

public abstract class RequestHandler

{

RequestHandler _successor;

public abstract void Handle(Request request);

public void ContinueWith(RequestHandler handler) { _successor = handler; }

protected void Next(Request request)

{

if (_successor != null)

_successor.Handle(request);

}

}

public class AuthenticationHandler : RequestHandler

{

public override void Handle(Request request)

{

if (!string.IsNullOrWhiteSpace(request.Username) &&

UserHasAccess(request.Username, request.Url))

Next(request);

else

throw new Exception("User not authenticated");

}

}

public class AuthorizationHandler : RequestHandler

{

public override void Handle(Request request)

{

if (request.IP.StartsWith("10."))

Next(request);

else

throw new Exception("Access Denied");

}

}

public class LoggingHandler : RequestHandler

{

public override void Handle(Request request)

{

//log the request here

Next(request);

}

}

To use the preceding structure, you can use the following code:

RequestHandler authenticationHandler = new AuthenticationHandler();

RequestHandler authorizationHandler = new AuthorizationHandler();

RequestHandler loggingHandler = new LoggingHandler();

authenticationHandler.ContinueWith(authorizationHandler); //Line 4

authorizationHandler.ContinueWith(loggingHandler); //Line 5

authenticationHandler.Handle(new Request //Line 6

{

IP = "192.168.1.1",

Username = "vahid",

Url = "http://abc.com/get"

});

As it is clear in lines 4 and 5 of the preceding code, each handler, using the ContinueWith method, has specified what handler should be the next node of the chain, and then in line 6, the execution of the chain is started, and then with the help of the Next method, this execution is carried out from node to node.

Participants:

Handler: In the preceding scenario, the RequestHandler is responsible for defining the format for processing requests.
Concrete handler: In the preceding scenario, it is the same as AuthenticationHandler, AuthorizationHandler, and LoggingHandler and is responsible for processing the relevant request. The behavior in this section is that if the handler is responsible for processing the request, it performs the processing; otherwise, it transfers the request to the next node.
Client: The same user is responsible for starting the request processing in the chain through the concrete handler.
Notes:

One of the tasks of the ConcreteIterator is to maintain the position of the current object.
Care should be taken not to form a loop in the design of the chain
Another example of implementing this design pattern can be Middleware in ASP.NET Core.
Handlers can be implemented using composite.
Consequences: Advantages

The request processing order can be controlled. For example, check IP access first, then authenticate the user, then control the access.
The Single Responsibility Principle (SRP) is upheld because the classes that are tasked with carrying out the operation are kept apart from the classes that are tasked with implementing it.
The open/close principle is met. It is possible to add new handlers to the chain or remove existing handlers without changing the user code. This increases flexibility.
Dependency is reduced. By using this design pattern, both the sender and receiver of the request do not have detailed information about each other and are not dependent on each other. Also, the input request has no information about the chain structure.
Consequences: Disadvantages

Some requests may not be processed due to not finding a suitable handler, and therefore there is no guarantee that the request will be processed.
Applicability:

When it is necessary to perform different processes on a request in order.
When you are faced with variable handlers while processing a request, the order of the handlers is not predetermined.
Related patterns:

Some of the following design patterns are not related to the chain of responsibility design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Composite
Decorator
Observer
Command
Command
Name:

Command

Classification:

Behavioral design patterns

Also known as:

Action/Transaction

Intent:

This design pattern tries to encapsulate a request or work in the form of an object. Further, this object can be queued and executed to process and perform assigned work. This feature makes it possible to undo a request before it is processed or interrupt the execution of the task.

Motivation, Structure, Implementation, and Sample code:

Suppose we are designing a system for the human resources unit of the organization. One of the stated requirements is that after hiring a new employee, it should be possible to define an email account, identification card, and business card for him. This requirement has been introduced in such a way that the human resource unit expert must register his request to perform all three tasks in the system. We also need to design the implementation of these three tasks in the form of a transaction so that if any one of the tasks is not done, the rest of the tasks are not done either.

With these explanations, it is clear that we are dealing with a series of commands, such as the command to create an email account, the command to create an ID card, and the command to create a business card. Each of these commands should be able to do two different things according to the requirement description. For example, the email account creation command must be able to create the email account and undo the creation of the email account. We are also faced with a set of commands. Each of these commands is a member of this set and starts to be executed in a way.

With these words, Figure 4.3 class diagram can be imagined:

Figure%204.3.png
Figure 4.3: Command design pattern UML diagram

As shown in the Figure 4.3 diagram, the expert of the human resources unit (Client) submits his request to the Recruitment class, and this class executes the commands through the commands that have been registered, and whenever a problem occurs, it undoes the operation he/she does. In this design, it is clear that the Client is not directly connected with the EmployeeManager, because the Client may not know the logic of executing tasks or canceling tasks. The Client only presents their request, and this request is queued and executed in the form of a series of commands. For Figure 4.3 design, the following codes can be imagined:

public interface IRecruitmentCommand

{

void Execute();

void Undo();

}

public class CreateEmailCommand : IRecruitmentCommand

{

EmployeeManager _manager;

public CreateEmailCommand(EmployeeManager manager) => _manager = manager;

public void Execute() => _ manager.CreateEmailAccount();

public void Undo() => _ manager.UndoCreateEmailAccount();

}

public class DesignIdentityCardCommand : IRecruitmentCommand

{

EmployeeManager _ manager;

public DesignIdentityCardCommand(EmployeeManager manager)

=> _manager = manager;

public void Execute() => _ manager.DesignIdentityCard();

public void Undo() => _ manager.UndoDesignIdentityCard();

}

public class DesignVisitingCardCommand : IRecruitmentCommand

{

EmployeeManager _ manager;

public DesignVisitingCardCommand(EmployeeManager manager)

=> _manager = manager;

public void Execute() => _ manager.DesignVisitingCard();

public void Undo() => _ manager.UndoDesignVisitingCard();

}

/*

For simplicity, the implementation for the methods of this class is not provided

*/

public class EmployeeManager

{

public EmployeeManager(int employeeId) { }

public void CreateEmailAccount() { }

public void UndoCreateEmailAccount() { }

public void DesignIdentityCard() { }

public void UndoDesignIdentityCard() { }

public void DesignVisitingCard() { }

public void UndoDesignVisitingCard() { }

}

public class Recruitment

{

public List Commands { get; set; }

= new List();

public void Invoke()

{

try

{

foreach (var command in Commands)

command.Execute();

}

catch //If an error occurs, all tasks must be canceled from the beginning

{

foreach (var command in Commands)

command.Undo();

}

}

}

To use this structure, you can do the following:

EmployeeManager employeeManager = new(1); // New employee with ID 1 is being recruited

Recruitment recruitment = new();

recruitment.Commands.Add(new CreateEmailCommand(employeeManager));

recruitment.Commands.Add(new DesignIdentityCardCommand(employeeManager));

recruitment.Commands.Add(new DesignVisitingCardCommand(employeeManager));

recruitment.Invoke();

As it is clear in the preceding code, the Client first creates an object of the type EmployeeManager. Then, instead of using the methods inside this class, it sets commands through Recruitment and then assigns the task of executing the request to Recruitment.

Participants:

Command: In the preceding scenario, it is the IRecruitmentCommand, which is responsible for defining the template for executing requests. The command usually declares methods such as Execute or Undo.
ConcreteCommand: In the preceding scenario, it is the same as CreateEmailCommand, DesignIdentityCardCommand, and DesignVisitingCardCommand, which is responsible for implementing the structure provided by the command. This implementation happens in the form of calling the desired work (Action) through the receiver object.
Invoker: In the preceding scenario, it is Recruitment that has the task of asking a command to execute the request.
Receiver: In the preceding scenario, it is the same as EmployeeManager and is responsible for implementing the logic of each task.
Client: It is the same user who is responsible for creating ConcreteCommand objects and sending them to the receiver.
Notes:

By using serialization, commands can be converted into strings and stored in the data source. They can even be sent over the network to a remote user.
To better implement the undo process, a class can be used as history. All the operations that are executed are stored in the form of a stack data structure. Next, you can perform the undo operation by scrolling the stack. The implementation of the undo operation with this design pattern does not seem simple because, during an operation, some private variables may have changed. To solve this problem, you can use the Memento design pattern.
Consequences: Advantages

The single responsibility principle is upheld because the classes that implement the operation are distinct from the classes that invoke the operation.
New commands can be defined and used without changing the existing codes. For this reason, the Open/Close principle is observed.
Undo and redo processes can be implemented.
It is possible to implement the operation with a delay.
Consequences: Disadvantages

Since a new layer is added between the sender and receiver, the complexity of the design increases.
Applicability:

When we need to take a break between tasks, or we have tasks that need to be executed according to a schedule.
When we need to have an operation with undo capability.
The handlers in the chain of responsibility design pattern can be implemented with the help of this design pattern.
To save a copy of the command, you can use the prototype design pattern.
When it is necessary to implement the call-back mechanism, commands are an object-oriented alternative to callbacks. This is achieved with the help of sending actions through parameters to objects.
Related patterns:

Some of the following design patterns are not related to the command design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Prototype
Composite
Memento
Observer
Chain of responsibility
Mediator
Name:

Mediator

Classification:

Behavioral design patterns

Also known as:

---

Intent:

This design pattern tries to communicate between different objects through a coordination center. The coordination center has the task of maintaining communication, and in this way, the applicant and the respondent can communicate and do not need information.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement has been raised in which it is requested to provide the possibility of conversation between users. Users will need to chat, specify the text of the message and the recipient of the message, send the message, and the other party will receive the message. For the design of this requirement, it is clear that if the users are in direct communication with each other, the complexity will be very high.

On the other hand, users can send and receive messages through an interface. In this case, users will not need to communicate with each other directly, and all their transactions will be done through the interface. So far, the scenario can be concluded that on one side of the design, we have the users who need to talk to each other, which is called colleagues, and on the other side, we have the communication interface, which is called the mediator. With the preceding explanations, the following class diagram can be imagined:

Figure%204.4.png
Figure 4.4: Mediator design Pattern UML diagram

As it is clear from the Figure 4.4 class diagram, different users can talk to each other, all of them are defined in this scenario through the Participant class and have implemented the IParticipant interface, and in this way, they provide their implementation for the Send and Receive methods. The responsibility of these two methods is to send and receive messages. On the other hand, a communication intermediary called Chatroom has been defined, which implements the IChatroom interface and provides its implementation of Login, Send, and Logout methods. The user sends a message through this method. For the Figure 4.4 class diagram, you can have the following code:

public interface IChatroom

{

void Login(IParticipant participant);

void Send(string from, string to, string message);

void Logout(IParticipant participant);

IParticipant GetParticipant(string name);

}

public class Chatroom : IChatroom

{

Dictionary participants = new();

public void Login(IParticipant participant)

{

if (!participants.ContainsKey(participant.Name))

participants.Add(participant.Name, participant);

}

public void Logout(IParticipant participant)

{

if (participants.ContainsKey(participant.Name))

participants.Remove(participant.Name);

}

public void Send(string from, string to, string message)

{

IParticipant receiver = participants[to];

if (receiver != null)

receiver.Receive(from, message);

else

throw new Exception("Invalid participant");

}

public IParticipant GetParticipant(string name)

=> participants.ContainsKey(name) ? participants[name] : null;

}

public interface IParticipant

{

public string Name { get; set; }

void Send(string to, string message);

void Receive(string from, string message);

}

public class Participant : IParticipant

{

private readonly IChatroom room;

public string Name { get; set; }

public Participant(string name, IChatroom room)

{

this.Name = name;

this.room = room;

}

public void Receive(string from, string message)

=> Console.WriteLine($"Sender: {from}, To: {Name}, Message: {message}");

public void Send(string to, string message)

=> room.Send(Name, to, message);

}

As can be seen in the preceding code, when the Send method is called in Participant, instead of directly delivering the request to the target, this method delivers the request to the room object. This object plays the role of mediator. Then, upon receiving the Send request, the room object calls the Receive method and delivers the message to the recipient. Also, the IChatroom interface in this example has an auxiliary method called GetParticipant, which is not related to the design and is only used to find other users.

Participants:

Mediator: In the preceding scenario, it is IChatroom and is responsible for defining the format for communication with colleagues.
ConceretMediator: In the preceding scenario, it is the Chatroom and is responsible for implementing the format provided by the mediator, and in this way, it communicates between colleagues. This class needs to know the colleagues in order to communicate.
Colleague: In the preceding scenario, it is the IParticipant and Participant who is always in contact with the Mediator, and whenever any of the colleagues needs to communicate with another colleague, they communicate through the mediator.
Client: It is the same user who executes the code through the mediator and ConcreteMediator.
Notes:

Mediator must implement collaborative communication between colleagues.
The purpose of this design pattern is to eliminate interdependencies.
When there is only one mediator, there is no need to define mediator and ConcreteMediator, and colleagues can directly communicate with ConcreteMediator. This principle also applies to colleagues.
Consequences: Advantages

By using this design pattern, communication between objects is transferred to a single place, and in this way, while complying with the single responsibility principle, maintaining the code is also easier. You can also focus on the communication logic of objects through Mediator and on the business of objects through colleagues.
Without the need to change the codes, a new mediator can be defined, and in this way, the Open/Close principle has been observed.
Stickiness between classes decreases, and colleagues communicate through the mediator.
Reusability is improved, and colleagues can be used by defining the new mediator.
By using this design pattern, many-to-many relationships become one-to-many relationships. One-to-many relationships are much simpler and easier to maintain and understand.
Consequences: Disadvantages

Over time, mediator objects themselves become objects that contain many complexities, which are called God objects, which can cause problems.
Applicability:

When it is not possible to change classes due to dependence on other classes, in this case, all communication is taken out of the class and established through a mediator.
When we need to use an object in another program, it is not possible due to many dependencies on other objects. In this case, with the presence of a mediator, communications are taken to this class, and therefore the class can be used in other programs as well. A sufficient condition to use the class in another application is to create a new mediator.
Related patterns:

Some of the following design patterns are not related to the mediator design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Facade
Observer
State
Name:

Adapter

Classification:

Behavioral design patterns

Also known as:

Objects for states

Intent:

This design pattern tries to change the behavior of the object by changing its internal state. In this case, different situations are presented in the form of different objects.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a requirement in which a workflow engine is requested to be produced for the support process of a company. The support process in this company is that the manager of the support unit sends a ticket to one of the experts from the list of registered tickets; the expert checks the ticket and takes the necessary action. Then it waits for the confirmation of the user who registered the ticket. After the end user's approval, the support manager makes a final check, the ticket is closed, and the next ticket is sent to the expert. This description of the state diagram of this process can be considered as follows:

Figure%204.5.png
Figure 4.5: Company's support process

To implement this entire structure, you can use only one class and control different states and movement between these states through if and switch blocks. In this way of implementation, by adding a new state to the process or applying changes to any of the existing states, the class codes will be changed, which will make the code testing process be done from the beginning. Therefore, this type of design will require code maintenance and development.

However, to design the preceding process, each situation can be defined as a class, and the business related to each situation can be encapsulated in that class. In this case, the problem raised about adding a new status, or changing the business of the existing status, will not affect the codes of other statuses. After each state is converted to a class, an interface is needed to process tickets. This interface class maintains the status of each ticket and allows the processing of each ticket.

According to the preceding explanations, the following class diagram can be imagined:

Figure%204.6.png
Figure 4.6: State design pattern UML diagram

As can be seen in the preceding class diagram, each of the states has become a series of classes that implement the ITicketState interface, and for this reason, they must provide their implementation for the Process method. Through this method, the work that needs to be done in any situation is carried out. Also, the TicketContext class contains the State variable through which it stores the state of each ticket. The Process method in this class is also responsible for using State to call the appropriate Process method in one of the AssignState, DoingState, ApprovalState, or ClosingState classes. According to these explanations, the following code can be imagined for Figure 4.6:

public interface ITikcetState

{

void Process(TicketContext context);

}

public class AssignState : ITikcetState

{

public void Process(TicketContext context)

{

Console.WriteLine("Ticket Assigned");

context.State = new DoingState();

}

}

public class DoingState : ITikcetState

{

public void Process(TicketContext context)

{

Console.WriteLine("Ticket Done");

context.State = new ApprovalState();

}

}

public class ApprovalState : ITikcetState

{

public void Process(TicketContext context)

{

Console.WriteLine("Ticket Approved");

context.State = new ClosingState();

}

}

public class ClosingState : ITikcetState

{

public void Process(TicketContext context)

{

Console.WriteLine("Ticket Closed");

context.State = new AssignState();

}

}

public class TicketContext

{

public ITikcetState State { get; set; }

public TicketContext(ITikcetState state) => this.State = state;

public void Process() => State.Process(this);

}

As can be seen in the preceding code, every time the Process method is called in the TicketContext class, the state of the ticket will change and go to the next state. To use the preceding code, you can do the following:

TicketContext context = new(new AssignState());

context.Process();

In the first line, the initial state is AssignState, so by calling the Process method in the second line, the Process method in AssignState will be executed. This method will also change the state of the ticket to DoingState after the work is done. Therefore, by calling the Process method in the TicketContext class again, this time, the Process method in the DoingState class will be executed, and this cycle will proceed in the same way.

Participants:

Context: In the preceding scenario, it is the TicketContext and is responsible for maintaining the current status. Also, this class is responsible for defining the appropriate framework for use by the client.
State: In the preceding scenario, ITicketState is responsible for defining the format for processing states.
ConcreteState: In the preceding scenario, it is AssignState, DoingState, ApprovalState, and ClosingState, which is responsible for implementing the business related to each state according to the format provided by State.
Client: It is the same user who executes the codes through context.
Notes:

Both the context class and the ConcreteState class can change the state of the object.
If the classes related to different states need context, according to the preceding scenario, you can send a context object to ConcreteState.
Both context and ConcreteState can determine the next state related to the current state based on the conditions.
The client sets the initial state through context. After the initial setting, the client no longer needs to interact directly with the state objects.
There are two modes regarding the creation and destruction of state objects. In the first case, the state object is created whenever necessary and destroyed after the work is done. The second state of the state object is never destroyed after it is created. The choice between these two modes depends on different modes. For example, if the status changes do not occur continuously or the statuses are unclear at the time of execution, the first mode is a suitable option. This mode prevents the creation of objects that are not used. But if the situation changes continuously, you can use the second mode.
State objects can often be defined using the singleton pattern.
Consequences: Advantages

Considering that the codes related to each situation are transferred to the corresponding molasses, the single responsibility principle has been observed.
Since by creating a new state, there is no change in the classes of the previous situations and the context class, the open/close principle has been observed.
By removing if blocks and similar blocks from the context class, existing codes become simpler, and maintenance and development are easier.
Consequences: Disadvantages

If the number of states is small, using this design pattern causes complexity.
Applicability:

It can be useful when an object should have different behaviors based on different states.
When an object has a large number of states and the codes for each state are constantly changing.
When there is a class in which there are a large number of if blocks or similar blocks that change the behavior provided by the methods of the class.
When there are duplicate codes for each situation.
Related patterns:

Some of the following design patterns are not related to the state design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Singleton
Flyweight
Strategy
Strategy
Name:

Strategy

Classification:

Behavioral design patterns

Also known as:

Policy

Intent:

This design pattern tries to separate different methods and algorithms of executing a task by forming an algorithm family. The formation of this family will make it possible to replace the algorithms with the least changes when using them.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement is raised during which it is requested to provide the possibility of outputting the data of a table. Table data can be exported to CSV, TXT, and XML formats. To implement this requirement, different output algorithms can be mentioned in one class. This method, like the problem discussed in the state design pattern, will cause changes in an algorithm or the addition of a new algorithm, leading to changes in class codes. This makes the code more difficult to maintain.

But it is possible to implement this method, similar to the approach that existed in the state design pattern, to implement each algorithm in a separate class. On the other hand, each algorithm forms a family by implementing and following an interface. The user can also communicate with this family by using the context class. In this way, applying a change in an algorithm or adding a new algorithm will affect only the same algorithm, and this will increase the maintainability and extensibility of the code.

According to the preceding explanations, the following class diagram can be imagined:

Figure%204.7.png
Figure 4.7: Strategy design pattern UML diagram

As can be seen in the Figure 4.7 class diagram, different algorithms include XMLExporter, CSVExporter, and TXTExporter classes, which all implement the IExporter interface and, in this way, provide their implementation for the Export method. The ExportContext class also takes the type of algorithm required by the user and calls the corresponding Export method using the Process method. This type of algorithm or work execution method is called strategy. For Figure 4.7 class diagram, the following code can be considered:

public interface IExporter

{

void Export(object data);

}

public class XMLExporter : IExporter

{

public void Export(object data)

=> Console.WriteLine("data exported to xml");

}

public class CSVExporter : IExporter

{

public void Export(object data)

=> Console.WriteLine("data exported to csv");

}

public class TXTExporter : IExporter

{

public void Export(object data)

=> Console.WriteLine("data exported to txt");

}

Public class ExportContext

{

private readonly IExporter _strategy;

public ExportContext(IExporter strategy) => this._strategy = strategy;

public void Process(object data) => _strategy.Export(data);

}

To use this code, you can do the following:

ExportContext export = new ExportContext(new XMLExporter());

export.Process(new { Name = "Vahid", LastName = "Farahmandian" });

Participants:

Strategy: In the preceding scenario, it is the same as IExporter and is responsible for defining the format for defining algorithms.
ConcreteStrategy: In the preceding scenario, it is XMLExporter, CSVExporter, and TXTExporter, and it is responsible for implementing the format provided by the strategy.
Context: Allows the client to use different algorithms. ConcreteStrategies can access the data of this class.
Client: The same user executes the code by presenting the strategy to the Context.
Notes:

Using inheritance, common codes between algorithms can be transferred to another class
Generics can also be used in context implementation. When the strategies are clear at the time of compilation, and there is no need to change them at the time of execution, then this method can be useful.
Sending strategy to context can be optional. In this case, if the user does not introduce the desired strategy, the context class will use the default strategy to advance the work.
There are different ways to implement this design pattern. One of these methods is using delegates. For this type of implementation, we have the following code:
public delegate void Export(object data);

public class Exporter

{

public static void XMLExport(object data)

{ Console.WriteLine("data exported to xml"); }

public static void CSVExport(object data)

{ Console.WriteLine("data exported to csv"); }

public static void TXTExport(object data)

{ Console.WriteLine("data exported to txt"); }

}

public class ExportContext

{

private Export _strategy;

public ExportContext(Export strategy) => this._strategy = strategy;

public void Process(object data) => _strategy(data);

}

which can be used as follows:

ExportContext export = new(new Export(Exporter.XMLExport));

export.Process(new { Name = "Vahid", LastName = "Farahmandian" });

This design pattern and the state design pattern have many similarities in the first point, but they have differences, including:
The state design pattern has to do with the internal state of a class, while the strategy pattern is just an algorithm that is used to do something and has nothing to do with the internal state of the class.
In the state design pattern, the state object usually needs a context to access information from the context. But in the strategy design pattern, each algorithm is an independent class and does not need client information.
In general, the state design pattern tries to do different things based on different situations, but the strategy design pattern tries to do the same thing with different existing methods.
Consequences: Advantages

It is possible to change the used algorithm during the execution.
The implementation details of an algorithm can be separated from the user of the algorithm.
It is possible to define a new algorithm without changing the existing codes or change the existing algorithm without changing other algorithms. For this reason, the Open/Close principle has been observed.
Consequences: Disadvantages

If we are dealing with a small number of algorithms that rarely change, using this pattern can increase the complexity.
The client needs to know the differences between the existing algorithms to be able to use them.
Applicability:

When we are faced with different algorithms to do the same task.
When the algorithms use data that the client should not have access to, by using this design pattern, the related and specific data of each algorithm are encapsulated in the class of that algorithm.
When several classes are similar and differ only in the way of implementing behaviors.
When we need to separate the business domain of a class from the algorithm implementation details, these algorithm implementation details are usually not important in the business domain of a class. For example, in a business related to a class, the sorting operation needs to happen somewhere. From the business point of view, it does not matter which algorithm the sorting function is, so, in this case, the strategy design algorithm can be useful.
When we are faced with a class that has many if blocks and similar blocks, and during these blocks, it tries to choose and execute one of the different algorithms.
Related Patterns:

Some of the following design patterns are not related to the strategy design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Decorator
Flyweight
State
Template method
Template method
Name:

Template method

Classification:

Behavioral design patterns

Also known as:

---

Intent:

This design pattern tries to define the main body by doing a task and assigning its implementation steps to the child classes.

Motivation, Structure, Implementation, and Sample code:

Suppose you are implementing the ability to enter data from different data sources into your system's database. Data can be imported from different sources and in different formats. For example, you may want to enter data from various sources in txt, XML, and CSV formats into your database.

In the preceding scenario, the overall structure of the work is fixed and will probably be similar to the following:

Figure%204.8.png
Figure 4.8: Data transformation process

The steps and the sequence are fixed for all source and destination formats, and the way of implementing these steps is different for each source type. For example, if the data source has CSV format, probably in the source data validation step, commas and columns should be checked. Or if the data is in XML format, then it is necessary to pay attention to the correctness of the XML format and the compatibility of some XSD structures.

With the preceding explanations, the following class diagram can be considered:

Figure%204.9.png
Figure 4.9: Template Method design pattern UML diagram

As it is clear in Figure 4.9 class diagram, the main body of the work is defined through the DataReader class. In this class, all methods except the Import method are abstract methods. The Import method is not abstract because child classes are not allowed to change it, and this method defines the sequence of work execution. This method is called the template method. The child classes inherit from the DataReader class and provide their implementation for each step. For the preceding class diagram, the following codes can be considered:

public abstract class DataReader

{

public void Import()

{

Connect();

Validate();

Read();

Convert();

}

public abstract void Connect();

public abstract void Validate();

public abstract void Read();

public abstract void Convert();

}

public class TXTReader : DataReader

{

public override void Connect()

=> Console.WriteLine("Connected to txt file");

public override void Validate()

=> Console.WriteLine("Data has valid txt format");

public override void Read()

=> Console.WriteLine("Data from txt file read done");

public override void Convert()

=> Console.WriteLine("Data converted from txt to destination type");

}

public class XMLReader: DataReader

{

public override void Connect()

=> Console.WriteLine("Connected to xml file");

public override void Validate()

=> Console.WriteLine("Data has valid xml format");

public override void Read()

=> Console.WriteLine("Data from xml file read done");

public override void Convert()

=> Console.WriteLine("Data converted from xml to destination type");

}

public class CSVReader : DataReader

{

public override void Connect()

=> Console.WriteLine("Connected to csv file");

public override void Validate()

=> Console.WriteLine("Data has valid csv format");

public override void Read()

=> Console.WriteLine("Data from csv file read done");

public override void Convert()

=> Console.WriteLine("Data converted from csv to destination type");

}

As can be seen in the preceding code, all the methods, except the Import method, are abstract. To use this structure, you can do the following:

DataReader reader = new CSVReader();

reader.Import();

Participants:

AbstractClass: In the preceding scenario, it is the same DataReader that is responsible for abstractly defining the steps of work execution. Also, this class is responsible for defining and implementing the template method. The template method is responsible for calling the steps based on the expected structure.
ConcreteClass: In the preceding scenario, it is TXTReader, XMLReader, and CSVReader. It is responsible for implementing the abstract steps defined by AbstractClass.
Client: The same user executes the code in the desired way.
Notes:

This design pattern creates an inverted control structure, which is called the Hollywood principle. This principle shows how the parent class can call the methods of the child class.
It is very important to identify hook and abstract methods in the template method implementation. Hook methods are methods that have a default implementation in the parent class, and child classes can override them. While abstract methods have no implementation, child classes must override them.
One should always try to keep the number of abstract methods in the template Method small.
The use of naming conventions can help the user identify the methods that should be overridden. For example, it can be a good idea to use the Do prefix before the names of abstract methods (DoRead, DoConvert, and so on). Of course, if you use Visual Studio to develop C# codes, it is possible to override the abstract methods of the parent in the child classes through this IDE.
The factory method is often called the template method.
Consequences: Advantages:

The user can define and implement certain parts of the algorithm based on his needs.
Reducing the size of the codes by transferring the common parts of the algorithm from the child classes to the parent class.
Consequences: Disadvantages

Limiting the user to the general structure provided.
As the number of abstraction steps increases, the code becomes more difficult to maintain.
Applicability:

When implementing an algorithm, we need to leave the implementation of some steps of the algorithm to the child classes to implement them based on their needs.
When several classes almost implement the same algorithm and differ only in a series of small parts. In this case, common codes can be removed from the child classes and placed in the parent class to prevent code rewriting.
Related patterns:

Some of the following design patterns are not related to the template method design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Factory method
Strategy
Observer
Conclusion
In this chapter, you got acquainted with the most famous behavioral design patterns and learned how to send requests along a chain of objects. We learned to encapsulate a request or task in the form of an object and how to facilitate communication between several objects by directing them toward a central object. We also learned to change the behavior of the object by changing its internal state and to separate different methods and algorithms for the execution of a task by forming an algorithmic family. We learned to assign the execution steps of a task to child classes while defining the main body of the execution of a task.

In the next chapter, you will learn about the rest of the behavioral 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

NET 7 Design Patterns In-Depth 3. Structural Design Patterns

Chapter 3
Structural Design Patterns
Introduction
Structural design patterns deal with the relationships between classes in the system. This category of design patterns determines how different objects can form a more complex structure together.

Structure
In this chapter, we will cover the following topics:

Structural design pattern
Adapter
Bridge
Composite
Decorator
Façade
Flyweight
Proxy
Objectives
By the end of this chapter, you will be familiar with structural design patterns and be able to understand their differences. It is expected that by using the points presented in this chapter, you will be able to design the correct structure for the classes and use each of the structural design patterns in their proper place.

Structural design patterns
In this category of design patterns, there are seven different design patterns, which are:

Adapter: Allows objects with incompatible interfaces to cooperate and communicate. This design pattern can exist in two different ways:
Adapter pipeline: In this case, several adapters are used together.
Retrofit interface pattern: An Adapter is used as a new interface for several classes.
Bridge: Separates abstractions from implementation. This design pattern can also be implemented in another way:
Tombstone: In this type of implementation, an intermediate object plays the seeker role, and through that, the exact location of the destination object is identified.
Composite: A tree structure can be created with a single mechanism.
Decorator: New behaviors and functions can be dynamically added to the object without inheritance.
Facade: It is possible to connect to a complex system by providing a straightforward method.
Flyweight: It provides the ability to answer many requests through the sharing of objects
Proxy: By providing a substitute for the object, you can do things like controlling access to the object
In addition to these seven GoF patterns, other patterns are about the structure of objects:

Aggregate pattern: It is a Composite with methods for aggregating children.
Extensibility pattern: Hides complex code behind a simple interface.
Marker pattern: An empty interface assigns metadata to the class.
Pipes and filters: A chain of processes in which each process's output is the next input.
Opaque pointer: Pointer to an undeclared or private type, to hide implementation details.
Adapter
In this section, the adapter design pattern is introduced and analyzed, according to the structure presented in GoF Design Patterns section in Chapter 1, Introduction to Design pattern.

Name:

Adapter

Classification:

Structural Design Patterns

Also known as:

Wrapper

Intent:

This design pattern tries to link two classes that are not structurally compatible with each other and work with them.

Motivation, Structure, Implementation, and Sample code:

Suppose a company has designed an infrastructure to connect and call web services, and through this infrastructure, it connects to various web services. This infrastructure is as follows:

Figure3.1.png
Figure 3.1: Client-External Service initial relation

In the current structure, the Client calls the services through an ExternalService object and according to the type of web service (let us assume it is only Get and Post). Currently, the codes on the client side are as follows:

IExternalService service = new ExternalService();

service.Url = “http://something.com/api/user”;

service.Get(“pageIndex = 1”);

This mechanism of calling a web service is used in most places of the software. After some time, the company concluded using an external server to connect to external services, and for this purpose, it bought a product called API Gateway. This product’s problem is connecting to the services purchased using the API Gateway. The class structure of the same is provided as follows:

Figure3.2.png
Figure 3.2: API Gateway Proxy class structure

Now, to oblige the client to use APIGatewayProxy, we will need to change many parts of the program, which will be a costly task that can naturally involve risks. It is possible to act another way and make the two classes APIGatewayProxy and ExternalService compatible with each other and finally work with them.

To establish this compatibility, we will need to define another class according to the provided IexternalService format and communicate with APIGatewayProxy through this class. In this case, while there will be much fewer changes on the client side (to the extent of changing the class name), we will be able to work with another class that is completely different from ExternalService in terms of the structure instead of working with ExternalService. According to the preceding explanations, the following class diagram can be considered:

Figure3.3.png
Figure 3.3: Adapter design pattern UML diagram

As seen in the preceding class diagram, the ServiceAdapter class is defined, which implements the IexternalService interface. This is because the ServiceAdapter class matches the ExternalService class. Then inside the ServiceAdapter class, APIGatewayProxy should be connected. For the preceding class diagram, the following code can be considered:

public interface IexternalService

{

public string Url { get; set; }

public Dictionary Headers { get; set; }

void Get(string queryString);

void Post(object body);

}

public class ExternalService : IexternalService

{

public string Url { get; set; }

public Dictionary Headers { get; set; }

public void Get(string queryString)

=> Console.WriteLine($”Getting data from: {Url}?{queryString}”);

public void Post(object body)

=> Console.WriteLine($”Posting data to: {Url}”);

}

public class APIGatewayProxy

{

public string BaseUrl { get; set; }

public void Invoke(

string action, object parameters, object body,

string verb, Dictionary headers

)

=> Console.WriteLine($”Invoking {verb} {action} from {BaseUrl}”);

}

public class ServiceAdapter : IexternalService

{

public string Url { get; set; }

public Dictionary Headers { get; set; }

public void Get(string queryString)

{

var proxy = new APIGatewayProxy(){ BaseUrl = Url[..Url.LastIndexOf(“/”)]};

proxy.Invoke(

Url[(Url.LastIndexOf(“/”) + 1)..],

queryString, null, “GET”, Headers);

}

public void Post(object body)

{

var proxy = new APIGatewayProxy(){ BaseUrl = Url[..Url.LastIndexOf(“/”)]};

proxy.Invoke(

Url[(Url.LastIndexOf(“/”) + 1)..],

null, body, “POST”, Headers);

}

}

As it is clear in the preceding code, ServiceAdapter communicates with APIGatewayProxy while implementing the IExternalService interface. In the preceding implementation, instead of creating an APIGatewayProxy object every time inside the methods, this can be done in the ServiceAdapter constructor, and that object can be used in the methods. Therefore, according to the preceding codes, the client can use these codes as follows:

IexternalService service = new ServiceAdapter();

service.Url = “http://something.com/api/user”;

service.Get(“pageIndex=1”);

As you can see, the preceding code is the same one presented at the beginning of the discussion, with the difference that it was made from the ExternalService object class at the beginning of the discussion. Still, in the preceding code, it is made from the ServiceAdapter object.

Participants:

Target: In the preceding scenario, it is the same as IexternalService, which is responsible for defining the client’s interface. This interface defines the communication protocol between Adaptee and the Client.
Adaptee: In the preceding scenario, it is the same as APIGatewayProxy, which means an interface must match the existing structure. Usually, the client cannot communicate with this class because it has a different structure.
Adapter: In the preceding scenario, it is the same as ServiceAdapter and is responsible for adapting the Adaptee to the Target.
Client: The same user uses objects compatible with Target.
Notes:

The vital point in this design pattern is that the client calls the desired operation in the Adapter, and this Adapter is responsible for calling the corresponding operation in the Adaptee.
The amount of work the adapter must do in this design pattern depends on the similarity or difference between the Target and the Adaptee.
To implement an Adapter, you can also use an inheritance called Class Adapter. Using this method, the Adapter inherits from the Adaptee class while implementing the interface provided by Target. If we assume that Target is a class, it would be impossible to implement this method in languages like C#, where multiple inheritances are not allowed.
The implementation presented in the previous section, in which the Adapter has an Adaptee object, is called Object Adapter.
The bridge design pattern can be very similar to the adapter design pattern, but these two have different purposes. The bridge design pattern tries to separate the structure from the implementation to enable these two parts to be developed independently. But the adapter tries to change the current interface structure.
The decorator design pattern tries to add a series of new features to the class without changing the interface structure. The proxy design pattern also tries to define a proxy for an object. Therefore, these two design patterns are different from the adapter design pattern.
In the preceding implementation, the intention was only to match Adaptee with Target. If there is a need for both the Adaptee to match the Target and the Target to match the Adaptee, then the Two-Way Adapter will need to be used. In this case, while implementing the Target, the adapter also inherits from the Adaptee. The prerequisite for implementing this method in languages like C# that does not support multiple inheritances is that the Target or Adaptee must be an interface. This method is briefly shown in the following code:
Public interface Target{

void A();

}

public class Adaptee{

Public void B(){}

}

Public class Adapter: Adaptee, Target{

Public void A(){}

}

An object can contain methods and variables. Methods are executable, and variables can be initialized and updated. With this description, if we store the method inside the variable, we can give more flexibility to the adapter design pattern. Because in this way, the method to be executed can be changed at the time of execution. In other words, in previous implementations of the adapter design pattern, the client needed to know the method’s name to use it. If it is possible to change the name of the method in question at the time of execution, it is possible to make the name of the method that the client calls and the method that exists in the Target different. In this case, the Adapter should be able to manage this name change. To implement this mechanism, you can use delegates in C# language. This way of implementing the adapter design pattern is also called Pluggable Adapter.
Public class Adaptee

{

public void Print(string message) => Console.WriteLine(message);

}

public class Target

{

public void Show(string input) => Console.WriteLine(input);

}

public class Adapter : Adaptee

{

public Action Request;

public Adapter(Adaptee adaptee) => Request = adaptee.Print;

public Adapter(Target target) => Request = target.Show;

}

The preceding code is a Two-Way Adapter implemented in a Pluggable way. As you can see, a Request is defined as an Action type and a different method is executed in the constructor according to the input sent. To use the preceding code, you can proceed as follows:

Adapter adapter1 = new(new Adaptee());

adapter1.Request(“Hello Vahid”);

Adapter adapter2 = new(new Target());

adapter2.Request(“Hi my name is Vahid”);

In the first line, an Adaptee object(adapter1) is sent to the Adapter, and then the Request is executed. This will make the Print method that is placed in the Request to be executed. In the next instance(adapter2), a Target object has been sent to the Adapter, which causes the Show method to be executed when the Request is executed. Using a two-way adapter to implement a pluggable adapter is not necessary. Apart from this method, there are other ways to implement pluggable adapter, including using abstract operations or a parametrized adapter.

Consequences: Advantages

Since the structure is separated from the business logic, SRP is followed.
You can add an adapter without changing the client codes. In this case, OCP has been observed.
Consequences: Disadvantages

It is necessary to define and add new classes and interfaces. It can sometimes increase the complexity of the code.
Applicability:

When we need to use an existing class, but the structure of this class does not match the existing codes.
When we have several classes inherited from a parent class and all lack a certain behavior. Assuming that this behavior cannot be placed in the parent class, Adapter can be used to cover this need.
Related Patterns:

Some of the following design patterns are not related to Adapter design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Bridge
Proxy
Decorator
Bridge
In this section, the bridge design pattern is introduced and analyzed, according to the structure presented in GoF design patterns section in Chapter 1, Introduction to Design Pattern.

Name:

Bridge

Classification:

Structural design patterns

Also known as:

Handle/Body

Intent:

This design pattern tries to separate the abstraction from the implementation and develop abstraction and implementation independently and separately. In other words, this design pattern tries to divide larger classes into two independent structures called abstraction and implementation. With this, each of these two structures can be developed independently. Consider two independent islands. Each of these islands can develop and change independently and a bridge has been created to connect these two islands.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement has been raised, and it is requested to implement the necessary infrastructure for a service bus. This service bus should be able to call the desired service and log the received result. To implement, the team concludes that they will first develop a service bus with minimum features and complete it in the following versions. Therefore, for the first version, the team only wants the service bus to save the received response’s log in a text file while running the service. For the second version, the team will try to provide the service bus with the ability of access control and then log in to the elastic database.

Considering the preceding scenario, one design option is to create a class for the first version, define the features in this class, and do the same for the second version through a separate class. To integrate different versions, these classes can inherit from a parent class. This design is based on inheritance and problem with this design, is that if we want to develop the connecting to services or develop the logs requirements, we will probably need to define a new class. Suppose in the third version, we want to add the log to the SQL Server database. We will need to define a new class and support this requirement.

A good design in this scenario is to separate the work of invoking the service and logging. Allowing the logging or connecting to services needs to be developed separately. For this relationship to be formed, the type of relationship changes from Inheritance to Aggregation, and this aggregation relationship plays the role of a bridge between these two separate islands. For each island, an interface is defined so that different implementations can happen according to these interfaces.

The following class diagram can be considered with the explanations provided in the preceding sections. The following class diagram shows that connecting to the service is separated from logging. The ILogger interface is defined to develop the log, and the IServiceBus interface to develop the connection to the services. The BasicBus and AdvandeBus classes implement the IServiceBus interface, and the TextLogger and ElasticLogger classes implement the ILogger interface. Also, IServiceBus is connected to ILogger through Logger. Therefore, the client can use his appropriate log class and gateway. Also, if we want to add the ability to log in to SQL Server, we can easily define this class, and there will be no need to change the bus section:

Figure3.4.png
Figure 3.4: Bridge design pattern UML diagram

For the preceding class diagram, the following codes can be considered:

public class Log

{

public DateTime LogDate { get; set; }

public string Result { get; set; }

public string Message { get; set; }

}

public interface ILogger

{

void Log(Log data);

}

public class TextLogger : ILogger

{

public void Log(Log data)

=> Console.WriteLine(

$"Log to text: {data.LogDate}-{data.Result}-{data.Message}");

}

public class ElasticLogger : ILogger

{

public void Log(Log data)

=> Console.WriteLine(

$"Log to elastic: {data.LogDate}-{data.Result}-{data.Message}");

}

public interface IServiceBus

{

public ILogger Logger { get; set; }

void Call(string url);

}

public class BasicBus : IServiceBus

{

public ILogger Logger { get; set; }

public void Call(string url)

{

Console.WriteLine($"Request sent to {url}");

Logger.Log(new Log {

LogDate = DateTime.Now,

Result = "OK",

Message = "Response received."

});

}

}

public class AdvanceBus : IServiceBus

{

public ILogger Logger { get; set; }

public void Call(string url)

{

if (new Uri(url).Scheme == "http")

Logger.Log(new Log {

LogDate = DateTime.Now,

Result = "Failed",

Message = "HTTP not supported!"

});

else

{

Console.WriteLine($"Request sent to {url}");

Logger.Log(new Log {

LogDate = DateTime.Now,

Result = "OK",

Message = "Response received."

});

}

}

}

As it is clear in the preceding code, the IServiceBus interface has a feature called Logger of ILogger type, which is connected to the logging mechanism. This connection is visible inside the Call method. Also, the two classes BasicBus and AdvanceBus have implemented the IServiceBus interface, the BasicBus class is the same as the first version and the AdvanceBus is the same as the second version. To use this structure, you can do the following:

IServiceBus bus = new BasicBus()

{

Logger = new TextLogger()

};

bus.Call("https://google.com");

According to the preceding code, if we want to save the log in Elasticsearch instead of text file, we can create object from ElasticLogger instead of creating object from TextLogger. Also, if we want to use the second version or AdvandeBus instead of the first version or BasicBus, we need to create an object from AdvanceBus instead of using the object from BasicBus.

Participants:

Abstraction: which is the same as IServiceBus in the preceding scenario. It is responsible for defining the format for abstractions. This interface should also provide a reference to the implementor.
RefinedAbstraction: the same as BasicBus and AdvanceBus in the preceding scenario. It is responsible for implementing the format provided by abstraction.
Implementor: which in the preceding scenario is ILogger. It is responsible for defining the format for implementations. This interface does not need to have a reference to abstraction.
ConcreteImplementor: which in the preceding scenario is the same as TextLogger and ElasticLogger, is responsible for implementing the template provided by implementor.
Client: It is the user and executes the code through abstraction.
Notes:

In this design pattern, abstraction is responsible for sending the request to the implementor.
Abstraction usually defines and implements a series of complex behaviors dependent on a series of basic behaviors defined through the implementor.
If we have more than one implementation, we cannot use implementor.
The important point in design pattern is when and from which implementor should the object be prepared? There are different ways to answer this question. One method is to prepare the appropriate object from implementor using the Abstract Factory design pattern, according to other features. In this case, the client will not be involved in the complexity of providing the object from the implementor.
Consequences: Advantages

It is possible to define a new RefinedAbstraction without changing the implementor. It is also possible to add new ConcreteImplementors without changing the abstraction. Hence, the OCP has been met.
Due to the separation of abstraction and implementor from each other, SRP has been observed.
Consequences: Disadvantages

Applying this design pattern to a code structure with many internal dependencies will increase complexity.
Applicability:

This design pattern can be used when faced with a class where different types of behavior have been implemented. Suppose there was a class that included the log operation in the text file and elastic together. Then, using this design pattern, we could separate this one-piece structure. The smaller the classes are, the easier it will be to debug and develop them.
When developing a class in different and independent dimensions, this design pattern can be used. For this purpose, each dimension can be defined and implemented in a separate structure.
If we need to move different implementations together during execution, we can benefit from this pattern.
Related patterns:

Some of the following design patterns are not related to bridge design pattern, but to implement this design pattern, the following design patterns will be useful:

Abstract Factory
Builder
Adapter
Composite
In this section, the composite design pattern is introduced and analyzed, according to the structure presented in GoF design patterns section in Chapter 1, Introduction to design pattern.

Name:

Composite

Classification:

Structural design patterns

Also known as:

---

Intent:

This design pattern tries to treat objects of the same type in a single form and turn them into a tree structure.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a requirement in which we need to give the user the possibility to define the program’s menus. Each menu can have sub-menus and menu members. Each member of the menu has text and address of the desired page, which should be directed to the desired page by clicking on it. In this scenario, we are facing a hierarchical or tree structure. For example, the following figure shows an example of this menu:

Figure3.5.png
Figure 3.5: Sample menu structure

In the preceding structure, the main menu has a member called Overview. This member has four other members: Intro, Architecture Components, Class Libraries and Tutorials. The Tutorials member has two other members: Template Changes and Use Visual Studio, of which Use Visual Studio again has three members: New Project, Debug and Publish. In other words, each member in the menu, according to the Figure 3.5, has three features: text, address and subcategory members. The leaves of the tree, in the preceding structure, do not have subgroup members and only have text and address.

With these explanations, we are faced with two entities. One entity for tree leaves and another for tree nodes. With these explanations, the following figure of class diagram can be imagined:

Figure3.6.png
Figure 3.6: Composite design pattern UML diagram

As shown in the preceding class diagram, there are two types of menu members. The first type: tree leaves and the second type: tree nodes. These different types, regardless of whether they are leaves or nodes, have text and address and can be printed. Menu class has the role of tree nodes and allows to add sub-set nodes or leaves to the nodes. For this reason, the Menu class, regardless of whether it implements the IMenuComponent interface, also has an aggregation relationship with this interface. The MenuItem class also has the role of a leaf in the preceding design. With the preceding explanations, the following code can be considered for the Figure 3.6:

public interface ImenuComponent

{

public string Text { get; set; }

public string Url { get; set; }

string Print();

}

public class Menu : ImenuComponent

{

public string Text { get; set; }

public string Url { get; set; }

public List Children { get; set; }

public string Print()

{

StringBuilder sb = new();

sb.Append($”Root: {Text}”);

sb.Append(Environment.NewLine);

foreach (var child in Children)

{

sb.Append($”Parent: {Text}, Child: {child.Print()}”);

sb.Append(Environment.NewLine);

}

return sb.ToString();

}

}

public class MenuItem : ImenuComponent

{

public string Text { get; set; }

public string Url { get; set; }

public string Print() => $”{Text}”;

}

To create a menu, with the structure presented at the beginning of the topic, you can proceed as follows:

ImenuComponent menu = new Menu()

{

Text = “Overview”,

Url = “/overview.html”,

Children = new List()

{

new MenuItem{Text =”Intro”,Url=”/intro.html”},

new MenuItem{Text =”Architecture Component”,Url=”/arch.html”},

new MenuItem{Text =”Class Libraries”,Url=”/class.html”},

new Menu{

Text =”Tutorials”,

Url=”/tutorials.html”,

Children=new List

{

new MenuItem{Text =”Template Changes”,Url=”/tpl.html”},

new Menu{

Text =”Use Visual Studio”,

Url=”/vs.html”,

Children=new List

{

new MenuItem{Text =”New Project”,Url=”/new-project.html”},

new MenuItem{Text =”Debug”,Url=”/debug.html”},

new MenuItem{Text =”Publish”,Url=”/publish.html”}

}

}

}

}

}

};

Console.WriteLine(menu.Print());

When the Print method is called in the last line, according to the nature of the menu object, which is of the Menu type, the Print method in this class is called. Within this method, according to the nature of each member (node or leaf), the corresponding Print method is called in the corresponding class.

Participants:

Component: In the preceding scenario, it is the same as ImenuComponent and is responsible for defining the common format for Leaf and Composite. If needed, component can be defined as an abstract class and default implementations can be placed in this class. Also, component should provide a template for member navigation. Being able to access the parent node is another task that the Component can provide, however this feature is optional for it.
Leaf: In the preceding scenario, it is the same as MenuItem and is responsible for implementing the leaf.
Composite: In the preceding scenario, it is the same as Menu and is responsible for the task of the node. Each node can have a subset and composite should provide appropriate capabilities for this purpose.
Client: It is the user and creates a tree structure through component.
Notes:

Using the composite design pattern, we have a primitive type (leaf) and a composite type (node). Both of these types implement a common interface. According to these points, a tree structure can be implemented using this design pattern.
Typically, the navigation method is such that the request is executed if it reaches the leaf. Otherwise, if it reaches the node, the request is sent to the subset nodes or leaves. This process continues until the leaf is reached. In this case, in the node, you can perform series of tasks before or after sending the request to the subset nodes or leaves.
By placing a reference to the parent node, it is possible to obtain both its subset and parent nodes through one node. This feature helps to implement the Chain of Responsibility design pattern.
Using the Flyweight design pattern, the parent can be shared between nodes or leaves.
To design the component as much as possible, common tasks should be placed in this class or interface. But sometimes, we face meaningless behaviors while composite and meaningful for others. For example, in the preceding scenario, children are only placed in composite, whereas we could have placed it in component and then provided it in the default implementation. What decision to make in these circumstances is a cost-benefit analysis. For example, in the preceding scenario, if we put the children attribute in the component, we would make the method of dealing with Leaf and Composite the same from the client's point of view. This creates transparency in the design. But on the other hand, it causes us to endanger the safety of the code or to allow the client to do meaningless things that cannot be detected at the time of compilation. This will also waste space(of course, this waste of space is very small).
On the other hand, by not placing children in the component, we have maintained the safety of the code, and the client cannot do meaningless tasks, and these tasks can be identified at the time of compilation. But in this case, we have lost the transparency. Therefore, the choice of the implementation method is very dependent on the cost-benefit analysis.

When you need to operate such as search and this operation is repeated many times, you can increase the overall efficiency by using caching.
The structure can be navigated using the Iterator design pattern.
By using the Visitor template, you can perform a task on all members.
Consequences: Advantages

The method of dealing with primitive and compound types is fixed from the client’s point of view, making it easier for the client.
You can add new members without changing the existing codes. This addition of new types occurs in a situation where the client is not involved in these events and in this way the OCP has been observed.
You can easily work with complex tree structures.
Consequences: Disadvantages

Although the possibility of adding new members can be considered an advantage, but from another point of view, this can also be a disadvantage. Because it is appreciated that sometimes we want to define restrictions on different members and specific members can only be added. Using this design pattern, solving this possibility requires runtime controls.
It may be not easy to define the component and put common behaviors; we have to define the component very general.
Applicability:

When we are faced with a tree structure of objects.
When we need, from the client’s point of view, the method of dealing with leaves and nodes seems fixed. This feature is achieved by implementing a single and common interface between nodes and leaves.
Related patterns:

Some of the following design patterns are not related to Composite design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Chain of Responsibility
Flyweight
Iterator
Visitor
Command
Builder
Decorator
Interpreter
Decorator
In this section, the decorator design pattern is introduced and analyzed, according to the structure presented in GoF design patterns section in Chapter 1, Introduction to Design patterns.

Name:

Decorator

Classification:

Structural design patterns

Also known as:

Wrapper1

Intent:

This design pattern tries to add some new functionality to the class at runtime or dynamically.

Motivation, Structure, Implementation, and Sample code:

Suppose we are designing a photo editing application and there is a requirement to convert the image to black-white or to add text on the image. There are different ways to solve requirement. One way is to go into the main class and add the necessary methods to black-white and add text. But this will make the main class complicated to maintain by adding more requirements.

Another way is to define a series of classes for black-white and adding text that inherit from the main class. In fact, in such scenarios where the capabilities of a class need to be developed, inheritance is one of the first ideas that comes to mind. But inheritance, in turn, has a series of points that must be considered before applying it to the design. For example, in many programming languages, a class can inherit

from only one class. Also, due to its nature, inheritance has a static nature, that is, by using inheritance, one behavior can be replaced by the behavior of the parent class, and the behavior of the parent class cannot be changed. Even the desired main class may be defined as Sealed and there is practically no possibility of inheriting from the class.

The third solution is to use aggregation or composition relationships to solve this problem. By using this type of a relationship, an object has a reference to the main object and in this way, it can do a series of additional tasks compared to the main object. Also, by using this solution, we will no longer face the limitation we had in inheritance. The only important point in this solution is that from the user’s point of view, the main and supplementary objects should look the same. To comply with this point, both the main class and the supplementary class implement a single interface. And finally, to be able to add a new feature to the main object, it is enough to send the main object to the supplementary class.

Therefore, with these explanations, the following class diagram can be considered:

Figure3.7.png
Figure 3.7: Decorator design pattern UML diagram

As shown in the diagram Figure 3.7, the Photo class is the main class that implements the IPhoto interface. If the client wants the original photo, he uses this class and receives the original image. On the other side is the PhotoDecoratorBase class. This class is considered abstract in the preceding diagram. This class implements the Iphoto interface like the Photo class and has an aggregation relationship with Iphoto. This means that by taking a Photo object, it can add a series of new features to it. Next, WatermarkDecorator and BlackWhiteDecorator are defined, which inherit from PhotoDecoratorBase. The important point here is that the GetPhoto method in the PhotoDecoratorBase class is defined as a virtual method so that each child class can change that behavior. With all these explanations, the client can deliver the main image to the decorators and, in this way, do the desired work on the photo, or it can do nothing with the decorators and work directly with the Photo class.

According to the preceding diagram, the following codes can be considered:

public interface Iphoto

{

Bitmap GetPhoto();

}

public class Photo : Iphoto

{

private readonly string filename;

public Photo(string filename) => this.filename = filename;

public Bitmap GetPhoto() => new(filename);

}

public abstract class PhotoDecoratorBase : Iphoto

{

private readonly Iphoto _photo;

public PhotoDecoratorBase(Iphoto photo) => _photo = photo;

public virtual Bitmap GetPhoto() => _photo.GetPhoto();

}

public class WatermarkDecorator : PhotoDecoratorBase

{

private readonly string text;

public WatermarkDecorator(Iphoto photo, string text)

: base(photo) => this.text = text;

public override Bitmap GetPhoto()

{

var photo = base.GetPhoto();

Graphics g = Graphics.FromImage(photo);

g.DrawString(

text,

new Font(“B Nazanin”, 18),

Brushes.Black,

photo.Width / 2,

photo.Height / 2);

g.Save();

return photo;

}

}

public class BlackWhiteDecorator : PhotoDecoratorBase

{

public BlackWhiteDecorator(Iphoto photo) : base(photo) { }

public override Bitmap GetPhoto()

{

var photo = base.GetPhoto();

//Convert photo to black and white here

return photo;

}

}

As can be seen in the preceding code, the Photo class has implemented the Iphoto interface. The PhotoDecoratorBase class also receives an Iphoto object while implementing this interface. Then the decorators inherit from this class and each one tries to provide its own functionality. Each decorator, while rewriting GetPhoto, first calls GetPhoto related to the Photo object and then adds a new feature to it. In order to use this code, you can do the following:

Iphoto photo = new Photo(“C:\\sample.png”);

photo.GetPhoto();//returns the original photo

WatermarkDecorator watermarkDecorator = new WatermarkDecorator(photo, “Sample Watermark”);

watermarkDecorator.GetPhoto();

BlackWhiteDecorator blackWhiteDecorator = new BlackWhiteDecorator(photo);

blackWhiteDecorator.GetPhoto();

In the first line, a Photo object is created and the GetPhoto method of the Photo class is called through this object. In the third and fourth lines, we give the object created in the first line to the WatermarkDecorator class and add text to the photo through that. In the fifth and sixth lines, we give the object created in the first line to the BlackWhiteDecorator and convert the image to black and white.

Participants:

Component: In the preceding scenario, it is the same as Iphoto and has the task of defining the template for the classes to which we want to add new features.
ConcreteComponent: In the preceding scenario, it is Photo that implements the template provided by component. In fact, this class is the same class to which we want to add a new feature.
Decorator: In the preceding scenario, the PhotoDecoratorBase has a reference to the component and tries to provide a template matching the template provided by the component.
ConcreteDecorator: In the preceding scenario, it is the same as WatermarkDecorator and BlackWhiteDecorator, which adds functionality to the component.
Client: It is the same user who uses the code through the component or decorator.
Notes:

In this design pattern, the decorator sends the incoming request to the component. In this process, it may perform some additional tasks before or after sending the request to the component.
Using this design pattern, there is more flexibility in adding or removing a feature than inheritance.
By using this design pattern and decorator, you can add features based on your requirements, and you don’t need to add all useless features to the class.
The decorator object must have an interface that matches the component interface.
If only one feature needs to be added, then there is no need to define the decorator abstract class.
The component class should be light and simple as much as possible, otherwise the decorator will have to implement features that it does not need, and this will make the decorator heavy and increase its complexity.
Using the Strategy design pattern, the internal structure and communications are changed and transformed, while by using the Decorator, only the procedure of the object is changed. For example, suppose we are dealing with a heavy and complex component. In this case, instead of defining a decorator for it, you can use Strategy to send a series of component capabilities to other objects. In this case, there is no need to transfer the weight of the component to the decorator.
Using this design pattern, only the procedure of the object is changed, so there is no need for the component to know about the decorators. This point is also one of the differences between Strategy and Decorator.
Adapter, Proxy and Decorator design patterns have similarities, but at the same time they have an important difference. The Adapter design pattern basically provides a different interface, the Proxy pattern provides exactly the same interface, and the Decorator pattern reinforces and provides the existing interface.
The Chain of Responsibility design pattern also has similarities with the Decorator design pattern. The difference between these two design patterns is that by using Chain of Responsibility, the chain of execution can be interrupted somewhere, but this is not possible in Decorator. Also, by using Chain of Responsibility, the execution of a task in one node of the chain is independent of its execution in another node. But in Decorator, the execution of a task depends on the main interface.
If there is a need to define different decorators, then these decorators can be independent from each other.
Consequences: Advantages

You can extend the behavior of an object without adding a child class.
It is possible to add or disable some features to the class at runtime.
Several behaviors can be combined with each other using decorator.
A large class can be divided into several small classes, each class has a single task, and in this way, SRP is observed.
Consequences: Disadvantages

Removing a Decorator has some complications.
Code debugging process becomes more complicated.
Applicability:

When we need to add a feature dynamically and subtly to a class and at the same time, we do not want to affect the previous objects.
When it is not possible to add new functionality to the class through inheritance.
Related patterns:

Some of the following design patterns are not related to Decorator design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Strategy
Composite
Chain of responsibility
Adapter
Proxy
Facade
In this section, the facade design pattern is introduced and analyzed, according to the structure presented in GoF design patterns section in Chapter 1, Introduction to Design Patterns.

Name:

Facade

Classification:

Structural design patterns

Also known as:

---

Intent

This design pattern tries to facilitate communication with different components of a system by providing a simple interface.

Motivation, Structure, Implementation, and Sample code:

In Figure 3.8 to use different components of the system, users are directly connected with these components, which increases the complexity. The Facade design pattern tries to manage this complexity by providing a simple interface, so that the user does not get involved in communication complications:

Figure3.8.png
Figure 3.8: Interactions in system without the presence of Facade

Therefore, in the presence of the Facade design pattern, the Figure 3.8 becomes as follows:

Figure3.9.png
Figure 3.9: Interactions in system with presence of Façade

In order to clarify the issue, pay attention to the following scenario:

Suppose there is a need in which the necessary infrastructure is needed to inquire about plane tickets from different airlines. Let us assume that we are dealing with three airlines, Iran Air, Mahan and Ata, and we need to inquire about the price of plane tickets from these airlines by providing the date, origin and destination, and then show the results of the inquiry to the user in order of cheap to expensive.

One view is that the user directly connects to each airline and performs the inquiry process. In this case, as in the Figure 3.8, there will be communication complexity and the user will be involved in how to connect and communicate with the airlines, which will increase the complexity and make the code more difficult to maintain.

According to the Figure 3.9, by providing a simple interface to the user, the way of communicating with the airline can be hidden from the user's view, and the user can only work with the provided interface and make ticket inquiries. With these explanations, the following class diagram can be imagined:

Figure3.10.png
Figure 3.10: Facade design pattern UML diagram

As shown in preceding figure diagram, the user submits the inquiry request to the TicketInquiry class, and this class communicates with each of the servers, collects the results, and returns them to the user. For the preceding design, the following code can be imagined:

public class Ticket

{

public DateTime FlightTime { get; set; }

public string FlightNumber { get; set; }

public string From { get; set; }

public string To { get; set; }

public int Price { get; set; }

public override string ToString()

=> $"{From}-{To}, {FlightTime}, Number:{FlightNumber},Price:{Price}";

}

public class TicketInquiry

{

public List Inquiry(DateTime date, string from, string to)

{

var iranAirFlights = new IranAir().SearchFlights(date, from, to);

var mahanFlights = new Mahan().Search(date, from, to);

var ataFlights = new ATA().Find(date, from, to);

List result = new();

result.AddRange(iranAirFlights);

result.AddRange(mahanFlights);

result.AddRange(ataFlights);

return result.OrderBy(x => x.Price).ToList();

}

}

public class IranAir

{

readonly Ticket[] iranairTickets = new[]

{

new Ticket() { FlightNumber = "IA1000", FlightTime = new DateTime(2021,01,02,11,20,00), Price = 800000, From="Tehran",To="Urmia" },

new Ticket() { FlightNumber = "IA2000", FlightTime = new DateTime(2021,01,02,12,45,00), Price = 750000, From="Tehran",To="Rasht" },

new Ticket() { FlightNumber = "IA3000", FlightTime = new DateTime(2021,01,03,09,10,00), Price = 700000, From="Tehran",To="Urmia" },

new Ticket() { FlightNumber = "IA4000", FlightTime = new DateTime(2021,01,02,18,45,00), Price = 775000, From="Tehran",To="Tabriz" },

new Ticket() { FlightNumber = "IA5000", FlightTime = new DateTime(2021,01,02,22,00,00), Price = 780000, From="Tehran",To="Ahvaz" },

};

public Ticket[] SearchFlights(DateTime date, string from, string to)

=> iranairTickets.Where(

x => x.FlightTime.Date == date.Date &&

x.From == from && x.To == to).ToArray();

}

public class Mahan

{

readonly Ticket[] mahanTickets = new[]

{

new Ticket() { FlightNumber = "M999", FlightTime = new DateTime(2021,01,03,13,30,00), Price = 1500000, From="Tehran",To="Zahedan" },

new Ticket() { FlightNumber = "M888", FlightTime = new DateTime(2021,01,04,15,00,00), Price = 810000, From="Tehran",To="Urmia" },

new Ticket() { FlightNumber = "M777", FlightTime = new DateTime(2021,01,02,06,10,00), Price = 745000, From="Tehran",To="Rasht" }

};

public Ticket[] Search(DateTime date, string from, string to)

=> mahanTickets.Where(

x => x.FlightTime.Date == date.Date &&

x.From == from && x.To == to).ToArray();

}

public class ATA

{

readonly Ticket[] ataTickets = new[]

{

new Ticket() { FlightNumber = "A123", FlightTime = new DateTime(2021,01,02,07,10,00), Price = 805000, From="Tehran",To="Urmia" },

new Ticket() { FlightNumber = "A456", FlightTime = new DateTime(2021,01,03,09,20,00), Price = 750000, From="Tehran",To="Sari" },

new Ticket() { FlightNumber = "A789", FlightTime = new DateTime(2021,01,02,16,50,00), Price = 700000, From="Tehran",To="Tabriz" },

new Ticket() { FlightNumber = "A159", FlightTime = new DateTime(2021,01,03,23,10,00), Price = 775000, From="Tehran",To="Sanandaj" },

new Ticket() { FlightNumber = "A357", FlightTime = new DateTime(2021,01,02,05,00,00), Price = 780000, From="Tehran",To="Urmia" },

};

public Ticket[] Find(DateTime date, string from, string to)

=> ataTickets.Where(

x => x.FlightTime.Date == date.Date &&

x.From == from && x.To == to).ToArray();

}

As can be seen in the preceding code, the user submits the request to TicketInquiry, and TicketInquiry is in charge of managing the relationship with the airlines and inquiries about the ticket and delivers the result to the user. In order to use this structure, you can proceed as follows:

TicketInquiry ticketInquiry = new TicketInquiry();

foreach (var item in ticketInquiry.Inquiry(new DateTime(2021,01,02),"Tehran","Urmia"))

{

Console.WriteLine(item.ToString());

}

In the preceding code, the user makes an inquiry through the TicketInquiry class and displays the received answer in the output.

Participants:

Facade: In the preceding scenario, it is TicketInquiry and is responsible for handling the client's request. In this responsibility, facade can connect to subsystems and track the request.
Subsystem classes: In the preceding scenario, it is IranAir, Mahan and ATA, which is responsible for implementing the business logic related to each subsystem. These classes should not have any knowledge of facade.
Client: It is the same user who delivers the request to facade and receives the response from facade.
Notes:

In order to prevent Facade from becoming complicated, it may be necessary to use several facades and each facade will implement the task of managing related requests.
If we want to hide the way of instantiating subsystems from the client's view, then we can use abstract factory instead of this design pattern.
Flyweight design pattern tries to create small and light objects, while facade design pattern tries to cover the whole system through one object.
Facade design pattern can be similar to mediator design pattern. Because both of these design patterns try to manage communication between classes. However, these two design patterns also have some differences:
The facade pattern only introduces a simple interface and does not provide any new functionality. Also, the subsystems themselves are unaware of the facade and have the possibility to communicate directly with another subsystems.
The mediator design pattern, on the other hand, depends on communication to a central core, and subsystems are not allowed to communicate directly with each other and can only communicate with each other through this central core.
In most cases, the facade class can be implemented as a singleton.
The facade design pattern has similarities with the proxy pattern. Because both of these patterns maintain a complex entity and create objects from it when necessary. However, unlike facade, the proxy design pattern has an interface that implements different services of this interface and therefore these services can be replaced with each other.
In order to reduce the dependency between client and subsystem, facade can be defined as an abstract class. In this case, different implementations can be provided for each facade and allow the Client to choose and use the most appropriate implementation among these different implementations.
If it is necessary to cut off the client's access to the subsystem, the subsystems can be defined as private classes in the Facade.
Consequences: Advantages

Due to the fact that the subsystem logic is isolated, it reduces the complexity of the current system. In fact, by using this design pattern, the client deals with fewer objects, making it possible to use subsystems easily.
There is a loose coupling between the client and subsystem.
Consequences: Disadvantages

Facade class can become God Object over time.

Applicability:

When there is a need to communicate with a specific and complex subsystem.
Related patterns:

Some of the following design patterns are not related to Decorator design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Singleton
Mediator
Abstract factory
Flyweight
Proxy
Flyweight
In this section, the flyweight design pattern is introduced and analyzed, according to the structure presented in GoF design patterns section in Chapter 1, Introduction to Design Pattern.

Name:

Flyweight

Classification:

Structural design patterns

Also known as:

---

Intent:

This design pattern tries to make optimal use of resources by sharing objects. Resources can be computing resources, memory or others.

Motivation, Structure, Implementation, and Sample code:

Suppose we are designing a computer game. In one sequence of this game, we need to design a forest and create and display two million trees in it. Suppose there are only pine trees in this forest. A pine tree has characteristics such as color, size (short, medium and tall), name (in this scenario we only have a pine tree) and coordinates.

To implement this scenario, one type of design is to define a class for the tree and then create the required number of objects from it. Suppose the following table is assumed for tree object properties:

Property

Data Type

Size

Name

String (20 chars)

40 Bytes

Color

String (20 chars)

40 Bytes

Size

String (5 chars)

10 Bytes

Coord_X

Integer

4 Bytes

Coord_Y

Integer

4 Bytes

Sum for only 1 tree

98 Bytes

Sum for all the 2 million trees

2,000,000x98=196,000,000 Bytes

Table 3.1: Detailed required memory

NOTE: For simplicity, Coord_X and Coord_Y are defined as integer. In a real-world example, you might need to use other data types for these attributes

According to the preceding table, to build two million trees, about 196 million bytes of memory will be needed.

But when we look at the preceding properties, we realize that some of these properties are common to all trees. For example, all the trees in the forest have three different sizes and have a fixed color for each size, and only the X and Y coordinates are different for each tree. Now, if there is a way to share this common data between trees, then there will be a significant reduction in memory consumption. For example, we can separate the fixed properties from the variable properties. After receiving the object related to the fixed properties, through the definition of a series of methods, we can set the variable properties. In this case, only one object of fixed properties is always created. So, the amount of memory consumption for fixed properties is as follows: (assume that all trees are made with a fixed size):

Attribute

Data Type

Size

Name

String (20 chars)

40 Bytes

Color

String (20 chars)

40 Bytes

Size

String (5 chars)

10 Bytes

Sum

90 Bytes

Table 3.2: Required memory for fixed properties

And for variable properties we also have:

Attribute

Data Type

Size

Coord_X

Integer

4 Bytes

Coord_Y

Integer

4 Bytes

Sum for only 1 tree

8 Bytes

Sum for all the 2 million trees

2,000,000x8=16,000,000 Bytes

Table 3.3: Required memory for variable properties

Finally, the total memory required will be around 16,000,090 Bytes. This number means about 180 million bytes of reduction in memory consumption. With the preceding explanations, the following class diagram can be considered:

Figure3.11.png
Figure 3.11: Flyweight design pattern UML diagram

As shown in the Figure 3.11 class diagram, the Tree class is used to create a tree and implements the ITree interface. This class has two sets of features. Fixed properties, such as Name, Color and Size, along with variable properties such as Coord_X and Coord_Y. The TreeFactory class is responsible for providing the tree object. In fact, this class is responsible for providing the tree object along with fixed properties. The Client class is responsible for setting the variable properties by receiving the object from the TreeFactory through the SetCoord method.

For Figure 3.11 class diagram, the following codes can be imagined:

public interface Itree

{

void SetCoord(int x, int y);

}

public class Tree : Itree

{

public string Name { get; private set; }

public string Color { get; private set; }

public string Size { get; private set; }

public int Coord_X { get; private set; }

public int Coord_Y { get; private set; }

public Tree(string name, string color, string size)

{

Name = name;

Color = color;

Size = size;

}

public void SetCoord(int x, int y)

{

this.Coord_X = x;

this.Coord_Y = y;

}

}

public class TreeFactory

{

readonly Dictionary _cache = new();

public Itree this[string name, string color, string size]

{

get

{

Itree tree;

string key = $”{name}_{color}_{size}”;

if (_cache.ContainsKey(key))

tree = _cache[key];

else{

tree = new Tree(name, color, size);

_cache.Add(key, tree);

}

return tree;

}

}

}

As can be seen in the preceding code, Name, Size and Color properties are fixed properties and Coord_X and Coord_Y properties are variable properties. An indexer is defined in the TreeFactory class, the job of this indexer is to provide or create a new object from Tree. In this indexer, if a tree with the given name, size and color has already been created, the same object is returned, otherwise, a new object is created and added to the object repository. When the user needs a Tree object, instead of directly creating the object, it is expected that the user will create the object through the TreeFactory class. To use this structure, you can proceed as follows:

TreeFactory treeFactory = new();

Itree tree = treeFactory[“pine”, “green”, “short”];

tree.SetCoord(1, 1);

Itree tree2 = treeFactory[“pine”, “green”, “short”];

tree2.SetCoord(2, 2);

Itree tree3 = treeFactory[“pine”, “green”, “short”];

tree3.SetCoord(3, 3);

As shown in the preceding code, in the second line, the object request for the tree named Pine, in green color and in short size, is registered. Since this object does not exist, TreeFactory creates a new object. In third line, the constructed tree is placed in coordinates x=1 and y=1. In fourth line, the tree with the same properties as before is requested again. The TreeFactory class, instead of creating a new object, provides the same object as the previous one, and this tree is also placed in the coordinates x=2 and y=2.

Participants:

Flyweight: In the preceding scenario, it is the same Itree that is responsible for defining the template for working on variable properties.
ConcreteFlyweight: In the preceding scenario, it is the same as Tree (Name, Size and Color properties), and it implements the interface provided by Flyweight. This class is responsible for maintaining static properties.
UnsharedConcreteFlyweight: In the preceding scenario, it is the same Tree (Coord_X and Coord_Y properties) and is responsible for implementing the interface provided by flyweight. In another design of this structure, you can separate the UnsharedConcreteFlyweight and ConcreteFlyweight classes and put a reference of ConcreteFlyweight in UnsharedConcreteFlyweight. This class is also called Context.
FlyweightFactory: In the preceding scenario, it is the same as TreeFactory and is in charge of maintaining, creating and presenting flyweight objects.
Client: It is the same user and adjusts variable properties through flyweight received from FlyweightFactory.
Notes:

There are two categories of properties related to this design pattern: intrinsic properties and extrinsic properties. Intrinsic properties, which we referred to as fixed properties, are shareable properties and are stored in ConcreteFlyweight, while extrinsic properties, which we refer to as variable properties, are non-shareable properties and are stored in UnsharedConcreteFlyweight. These properties can also be calculated on the client side and sent to ConcreteFlyweight.
Client should not directly create object from ConcreteFlyweight and should only obtain this object through FlyweightFactory. In this case, the object sharing mechanism is always observed and applied.
Although the preceding structure has implemented the flyweight design pattern, it still needs some help. The first problem is that the user has access to the Tree class and can create objects directly from this class. To solve this problem, you can put the Tree class as a private class inside the TreeFactory class. Refer to the following code:
public class TreeFactory

{

private class Tree : Itree

{

public string Name { get; private set; }

Now, with this change, the user will not have direct access to create objects from the Tree class.

The next problem with the preceding structure is that the objects provided by TreeFactory for the same key, are always same. This means that the change of an attribute in an object is reflected in other objects as well.
TreeFactory treeFactory = new();

Itree tree = treeFactory[“pine”, “green”, “short”];

tree.SetCoord(1, 1);

Itree tree2 = treeFactory[“pine”, “green”, “short”];

tree2.SetCoord(2, 2);

Itree tree3 = treeFactory[“pine”, “green”, “short”];

tree3.SetCoord(3, 3);

In the preceding code, in the third line, the SetCoord method sets the values of Coord_X and Coord_Y with the value 1. But in the fifth line, these values are shifted by 2, and this makes tree1 and tree2 objects see both Coord_X and Coord_Y attribute values equal to 2. To solve the problem, it will be necessary to separate the storage location of fixed properties and variable properties. Pay attention to the following code:

public class TreeType : Itree

{

public string Name { get; private set; }

public string Color { get; private set; }

public string Size { get; private set; }

public TreeType(string name, string color, string size)

{

Name = name;

Color = color;

Size = size;

}

public void Draw(Itree tree)

{

var obj = (Tree)tree;

Console.WriteLine($”

TreeType:{GetHashCode()},{Name},

Tree:{obj.GetHashCode()}({obj.Coord_X},{obj.Coord_Y})”);

}

}

In the preceding code, the fixed properties are completely separated from the variable properties. Fixed properties are moved into the TreeType class and variable properties are placed into the Tree class. TreeFactory class creates or provides TreeType object through indexer. To use this structure, you can do the following:

List trees = new();

TreeType type = new TreeFactory()[“pine”, “green”, “short”];

Tree tree1 = new(type, 1, 1);

trees.Add(tree1);

Tree tree2 = new(type, 2, 2);

trees.Add(tree2);

Tree tree3 = new(type, 3, 3);

trees.Add(tree3);

foreach (var item in trees)

{

item.Draw(item);

}

In second line, the TreeFactory object with the provided properties is requested. Then this object has been sent to the Tree class in lines 3, 5 and 7 to create the desired trees in the desired coordinates. Now, the type variable, which is of TreeType type, is shared between all three objects, tree1, tree2, and tree3. The output of the preceding code will be as follows:

TreeType:58225482,pine,Tree:54267293(1,1)

TreeType:58225482,pine,Tree:18643596(2,2)

TreeType:58225482,pine,Tree:33574638(3,3)

As it is clear in the output, only one object of TreeType type is created and it is shared between three objects tree1, tree2 and tree3. This sharing will greatly reduce the consumption of resources such as memory.

This design pattern is very similar to singleton design pattern. However, there are some differences between these two design patterns. Including:
In singleton, there is only one object, but in flyweight, there is one object per fixed property class.
The object created through singleton is mutable while the object created by flyweight is immutable.
Same as the singleton design pattern, concurrency topics should be considered in this pattern.
Consequences: Advantages

Memory consumption is significantly reduced.
Consequences: Disadvantages

Code complexity increases.
Applicability:

When there is a need to create a large number of objects and memory is not available for this amount of created objects.
Related patterns:

Some of the following design patterns are not related to Flyweight design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Singleton
State
Strategy
Interpreter
Composite
Facade
Proxy
In this section, the proxy design pattern is introduced and analyzed, according to the structure presented in GoF design patterns section in Chapter 1, Introduction to Design Pattern.

Name:

Proxy

Classification:

Structural design patterns

Also known as:

Surrogate

Intent:

This design pattern tries to control the use of an object by providing a substitute for it.

Motivation, Structure, Implementation, and Sample code:

Suppose we are designing an infrastructure to communicate with a GIS service. For the sake of simplicity. Let us assume that the requirement is such that we need to provide the name of a geographic region (country, city, street, and so on.), and receive information about the latitude and longitude of that region.

In order to implement this mechanism, one option is to connect the user directly to the GIS service. But by doing this, we notice the slow response, because for every request sent to the GIS service, this service tries to find the latitude and longitude from the beginning.

Another way to implement this requirement is to send the requests to an intermediary instead of directly from the user to the GIS service, and then send the request to the GIS service through that intermediary. This is called a proxy. By doing this, we will get several advantages, including being able to cache requests and responses for some popular requests from the proxy side before sending them to GIS. With this, we will no longer need to submit any request to the GIS service. Also, in the presence of the proxy, you can record the request and response logs. In the presence of proxy, requests can be controlled.

According to the preceding explanations, the following class diagram can be imagined:

Figure3.12.png
Figure 3.12: Proxy design pattern UML diagram

As shown in the Figure 3.12 class diagram, the GISService class has a method called GetLatLng, which takes the name of the geographic region and returns the longitude and latitude of that region. We need to connect the user through a proxy instead of directly connecting to the GISService. For this, in order for the user to avoid being involved in changes and design details, it is necessary to design the GISServiceProxy class in such a way that its methods have a proper mapping with GISService. For this, the IGISService interface is defined and both GISService and GISServiceProxy classes have implemented this interface. In this case, the user can easily connect to the proxy, and perform the necessary control and monitoring tasks in the proxy, and finally, the request is sent to GISService if needed, and the response is received by the proxy and delivered to the user. According to these explanations, the following codes can be considered:

public interface IGISService

{

string GetLatLng(string name);

}

public class GISService : IGISService

{

static readonly Dictionary map = new()

{

{ "Tehran", "35.44°N,51.30°E" },

{ "Urmia", "37.54°N,45.07°E" },

{ "Khorramabad", "33.46°N,48.33°E" },

{ "Shahrekord", "32.32°N,50.87°E" },

{ "Zahedan", "29.45°N,60.88°E" },

{ "Ilam", "33.63°N,46.41°E" },

{ "Yasuj", "30.66°N,51.58°E" },

{ "Ahvaz", "31.31°N,48.67°E" },

{ "Rasht", "37.26°N,49.58°E" },

{ "Sari", "36.56°N,53.05°E" },

{ "Sanandaj", "35.32°N,46.98°E" },

{ "Ardabil", "37.54°N,45.07°E" }

};

public string GetLatLng(string name)

{

Thread.Sleep(5000);

return map.FirstOrDefault(x => x.Key == name).Value;

}

}

public class GISServiceProxy : IGISService

{

static readonly Dictionary mapCache = new();

static readonly GISService _gisService = new();

public string GetLatLng(string name)

{

var requestOn = DateTime.Now.TimeOfDay;

if (!mapCache.ContainsKey(name))

{

string latlng = _gisService.GetLatLng(name);

if (!string.IsNullOrWhiteSpace(latlng))

mapCache.TryAdd(name, latlng);

else

throw new Exception("Given Geo not found!");

}

var responseOn = DateTime.Now.TimeOfDay;

return

$"Geo:{name},

Sent:{requestOn},

Received:{responseOn},

Response:{mapCache[name]}";

}

}

In the preceding code, for the purpose of simulation, within the GetLatLng method in the GISService class, a five-second delay is considered for each incoming request. However, if we pay attention to the implementation of this method in the GISServiceProxy class, we will notice that the responses received from the GISService are somehow cached, and subsequent requests for a fixed geographic region are answered without referring to the GISService, so we face an increase in the response speed. In order to execute the preceding code, you can do as follows:

IGISService gisService = new GISServiceProxy();

Console.WriteLine(gisService.GetLatLng(“Urmia”));

Console.WriteLine(gisService.GetLatLng(“Tehran”));

Console.WriteLine(gisService.GetLatLng(“Urmia”));

In the preceding code, when the Urmia query is sent for the first time, the response to this query is received with a delay of five seconds. This is while Urmia's re-query is no longer delayed and the direct response from the cache is returned by the proxy. Therefore, for the preceding code, we will have the following output:

Geo:Urmia,Sent:16:56:47.5390793,Received:16:56:52.5762895,
Response:37.54°N,45.07°E

Geo:Tehran,Sent:16:56:52.5932198,Received:16:56:57.6020481,
Response:35.44°N,51.30°E

Geo:Urmia,Sent:16:56:57.6052643,Received:16:56:57.6053073,
Response:37.54°N,45.07°E

In the preceding output, it can also be seen that, the Urmia query was sent at 16:56:47 and its response was received at 16:56:52, that is, about five seconds later, while the second query of the same city, without a five second delay, sent at 16:56:57 and received a response within a few milliseconds.

Participants:

Subject: which is the same as IGISService in the preceding scenario, is responsible for defining the format based on RealSubject. Using the provided template, wherever RealSubject is needed, it can be replaced with proxy.
Proxy: which is the GISServiceProxy in the preceding scenario, is in charge of communicating with RealSubject based on the format provided by Subject. This communication can be done for various purposes such as access control, logging, and so on. Proxy, methods are defined like RealSubject methods, so that the client does not notice possible changes.
RealSubject: In the preceding scenario, it is the same as GISService, it is responsible for defining the real object based on the format provided by subject. The proxy object is supposed to be replaced with this object.
Client: It is the same user who executes the code through proxy.
Notes:

Different types of proxy can be defined, including:
Remote proxy: In this type of proxy, subject and proxy are located in two different places, and in fact, we are trying to define a local proxy for the subject that is in another place. This type of proxy is very similar to the concept of using a web service. For example, suppose the client intends to use two web services provided at an external address. In this case, the task of communicating with the external service and related settings can be defined in a remote proxy, and the client communicates with the proxy instead of needing to communicate with the web service while repeating the code. Therefore, the following code can be considered for it:
public class RemoteProxy : IRemoteService

{

readonly HttpClient _client;

public RemoteProxy() => _client = new HttpClient

{

BaseAddress = new Uri("https://jinget.com/api")

};

public async Task GetAsync(int id)

=>await (await

_client.GetAsync($"/users/{id}")).Content.ReadAsStringAsync();

public async Task GetAllAsync()

=>await (await

_client.GetAsync("/users")).Content.ReadAsStringAsync();

}

In the preceding code, the Subject is located at a remote address at https://jinget.com.

Virtual proxy: This type of proxy can be used when the process of creating an object from a subject is time-consuming or expensive. In this case, the object is created from the subject only when it is really needed. This type of proxy is also called Lazy initialization proxy.
Protection proxy: This type of proxy can be used when we intend to control access to the requested resource. For example, by using this type of proxy, it is possible to check whether the client has the right to access this resource before connecting to the subject.
Smart proxy: This type of proxy is usually used when we need to do some specific tasks before accessing the subject object. For example, we may need to prevent the creation of multiple objects from subject through singleton implementation.
Logging proxy: There are times when we need to log all references to subject.
Caching proxy: There are times when we need to save the results of references to subject. In the scenario mentioned preceding, this type of proxy has been used.
When using this design pattern, not necessarily all requests may be sent to subject.
By using the copy on write technique, you can prevent the creation of additional objects. Using this technique, a new object is created when there is a need to change the object, otherwise the existing object is shared among all.
Using this design pattern, proxy can only sometimes know the type of subject. When the proxy needs to create an object from the subject, it needs to know its type, otherwise it does not need to know the subject type.
Consequences: Advantages

The object related to RealSubject can be controlled without the client noticing. This control can even include the control of the life span of the object.
Without the need to touch RealSubject codes and change them, a new Proxy can be created, and therefore OCP has been observed.
By using a remote proxy, the fact that the subject is located in another place is hidden from the client's view.
By using a virtual proxy, you can create the subject object if needed or reuse the existing objects. This can improve performance.
Consequences: Disadvantages

Due to the definition of many classes and methods, it can lead to an increase in code complexity.
This template can cause a delay in receiving the final response.
Client may use RealSubject in some places and proxy in some other places, which causes confusion in the structure and code.
Applicability:

When it is necessary to perform a series of tasks such as: access control, caching, logging, and so on before connecting to the subject, then this design pattern can be used.
When we need to use a series of services located at an external address (such as web service, connection with Windows services, and so on), we can use this design pattern.

Related patterns:

Some of the following design patterns are not related to Proxy design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Adapter
Decorator
façade
Conclusion
In this chapter, you learned about structural design patterns and learned how to manage the connections between classes and objects for different scenarios. In the next chapter, you will learn about behavioral design patterns and learn how to deal with behavior of objects and classes.

1 Adapter design pattern is also known as Wrapper too.

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

NET 7 Design Patterns In-Depth 2. Creational Design Patterns

Chapter 2 Creational Design Patterns
第2章 创建式设计模式

Introduction

简介

As the name suggests, creational design patterns deal with object construction and how to create instances. In C# .NET programming language, a new keyword is used to create an object. To create an object, it is necessary to provide the name of the class. In other words, wherever an object is needed, it can be created using the new keyword and the class name. However, there are instances where it is necessary to hide the way the object is made from the user's view. In this case, creational design patterns can be useful.

顾名思义,创建式设计模式处理对象构造以及如何创建实例。在 C# .NET 编程语言中,new 关键字用于创建对象。要创建对象,必须提供类的名称。换句话说,只要需要对象,就可以使用 new 关键字和类名来创建它。但是,在某些情况下,有必要从用户的视图中隐藏对象的创建方式。在这种情况下,创建式设计模式可能很有用。

Structure

结构

In this chapter, we will cover the following topics:

在本章中,我们将介绍以下主题:

  • Creational design pattern
    创建式设计模式

  • Factory method
    工厂方法

  • Abstract factory
    抽象工厂

  • Builder
    Builder

  • Prototype
    原型

  • Singleton
    单例

  • Objectives
    目标

By the end of this chapter, you will be familiar with creational design patterns and be able to understand their differences. It is also expected that by using the tips provided in this chapter, you can use each creational design pattern in its proper place.

在本章结束时,您将熟悉创建式设计模式并能够理解它们的差异。此外,通过使用本章中提供的提示,您可以在适当的位置使用每个创建性设计模式。

Creational design pattern

创建式设计模式

Using creational design patterns, you can understand which object was made by whom, how, and when. In the category of creational design patterns in GoF, five design patterns have been introduced, which are:

使用创建性设计模式,您可以了解哪个对象是由谁、如何以及何时制造的。在 GoF 的创建性设计模式类别中,引入了五种设计模式,它们是:

  • Factory method: Tries to select a class from several existing classes and create an object.
    Factory method:尝试从多个现有类中选择一个类并创建一个对象。

  • Abstract factory: Tries to create objects from classes of the same family.
    抽象工厂:尝试从同一系列的类创建对象。

  • Builder: Tries to separate the object’s construction from its presentation.
    生成器:尝试将对象的构造与其表示分开。

  • Prototype: Tries to make a copy of an existing object.
    原型:尝试创建现有对象的副本。

  • Singleton: Tries to have only one object of the class.
    Singleton:尝试只具有类的一个对象。

These five design patterns can be used interchangeably and can be complementary in some situations. For example, the prototype and abstract factory design patterns may be useful in some situations. Or the singleton pattern may be used in the prototype implementation to make the prototype implementation more complete.

这五种设计模式可以互换使用,并且在某些情况下可以互补。例如,原型和抽象工厂设计模式在某些情况下可能很有用。或者可以在 prototype 实现中使用 singleton 模式,以使 prototype 实现更加完整。

Usually, the creational design patterns can be used for the following conditions:
通常,创建性设计模式可用于以下条件:

  • When the system needs to be independent of how the objects are made
    当系统需要独立于对象的创建方式时

  • When a set of objects are to be used together
    当一组对象一起使用时

  • When there is a need to hide the class implementation from the user's view
    当需要从用户视图中隐藏类实现时

  • When there is a need for different presentations of a complex object.
    当需要对复杂对象进行不同的表示时。

  • Sampling should be clear at the time of execution.
    在执行时应明确抽样。

  • When only one object is required to be provided to the user.
    当只需要向用户提供一个对象时。

Apart from the five GoF patterns, other patterns are related to the creation of objects, such as:

除了五种 GoF 模式外,其他模式与对象的创建有关,例如:

  • Dependency injection pattern: Instead of creating an object, the class receives the required object through the Injector.
    依赖注入模式:该类不是创建对象,而是通过 Injector 接收所需的对象。

  • Object pooling pattern: It prevents the object from being destroyed and recreated and reuses the existing objects in the recovery method.
    对象池模式:它可以防止对象被销毁和重新创建,并在恢复方法中重用现有对象。

  • Lazy initialization pattern: Delays the creation of the object until it is used.
    延迟初始化模式:延迟对象的创建,直到使用它。

Factory method

工厂方法

This section introduces and analyzes the factory method design pattern according to the structure presented in the GoF design patterns section in Chapter 1, Introduction to Design Patterns.

本节根据第 1 章 设计模式简介的 GoF 设计模式部分中介绍的结构介绍和分析工厂方法设计模式。

Name:名字
Factory method工厂方法

Classification:分类
Creational design patterns创建式设计模式

Also known as:也称为
Virtual constructor虚拟构造函数

Intent:意图
This design pattern tries to delegate the creation of objects to child classes in a parent-child structure.此设计模式尝试将对象的创建委托给父子结构中的子类。

Motivation, Structure, Implementation, and Sample code:
动机、结构、实现和示例代码:

Suppose we are building a residential unit and must make a "door". To make a "door", we must refer to a "door manufacturer", but so far, we have no idea what type of door we need (wooden, aluminum, iron, and so on). Secondly, we do not know which manufacturer we should refer to (Wooden or iron door manufacturer, and so on). So, we are facing two abstractions here: "door" and "door manufacturer":

假设我们正在建造一个住宅单元,并且必须制作一扇“门”。要制作“门”,我们必须参考“门制造商”,但到目前为止,我们不知道我们需要什么类型的门(木门、铝门、铁门等)。其次,我们不知道我们应该参考哪个制造商(木门或铁门制造商,等等)。因此,我们在这里面临两个抽象:“door” 和 “door manufacturer”:

alt text
Figure 2.1: Door-Door manufacturer relation
图 2.1: 门-门制造商关系

According to Figure 2.1, the following code could be considered:
根据图 2.1,可以考虑以下代码:


public abstract class Door
{
    public abstract void Design();
    public abstract void Build();
    public abstract void Coloring();
}

public abstract class DoorManufacturer
{
    public Door CreateDoor(){}
}

According to the preceding paragraph, we finally decided to choose a "wooden door" for our unit, so we must go to a "carpenter" to make a "wooden door". It is clear that "wooden door" is a type of "door" and "carpenter" is a type of "door maker". So, we have two classes WoodenDoor for "wooden door" and Carpenter for "carpenter".

根据前一段,我们最后决定为我们的单位选择一扇“木门”,那么我们必须去找一个“木匠”来制作一扇“木门”。很明显,“木门”是“门”的一种,而“木匠”是“造门师”的一种。因此,我们有两个类 WoodenDoor 用于 “wooden door” 和 “carpenter” 的 Carpenter。

On the other hand, we know that Design, Build, and Coloring behaviors can be defined for doors. We still do not know exactly how these behaviors happen for different types of doors (wooden, iron, and so on). To solve this problem in the WoodenDoor class, we need to implement these behaviors to determine how each behavior happens for a wooden door. A similar mechanism should be implemented for other types of doors, such as iron doors.

另一方面,我们知道可以为门定义 Design、Build 和 Coloring 行为。我们仍然不知道这些行为对于不同类型的门(木制、铁制等)是如何发生的。要在 WoodenDoor 类中解决此问题,我们需要实现这些行为来确定木门的每种行为是如何发生的。其他类型的门也应采用类似的机制,例如铁门。

We also know that a door manufacturer must be able to make a door. To make a door, he must design, build, and color, but we have yet to learn of a door manufacturer. To solve this problem, we add a method called GetDoor to the DoorManufacturer class and specify that this method is responsible for creating a "door manufacturer" type object. Finally, every door manufacturer (such as a wooden door manufacturer) implements this method according to his/her requirements and processes. Therefore, through this implementation in the DoorManufacturer class, we find out which manufacturer intends to make the door:

我们也知道,门制造商必须能够制造门。要制造门,他必须设计、建造和着色,但我们还没有了解到门制造商。为了解决此问题,我们将一个名为 GetDoor 的方法添加到 DoorManufacturer 类中,并指定此方法负责创建“门制造商”类型对象。最后,每个门制造商(例如木门制造商)都根据他/她的要求和流程实施这种方法。因此,通过 DoorManufacturer 类中的这个实现,我们找出哪个制造商打算制造门:

public abstract class Door
{
    public abstract void Design();
    public abstract void Build();
    public abstract void Coloring();
}

public class WoodenDoor : Door
{
    public override void Design()
    {
        Console.WriteLine("Wooden door design done");
    }

    public override void Build()
    {
        Console.WriteLine("Wooden door build done");
    }

    public override void Coloring()
    {
        Console.WriteLine("Wooden door coloring done");
    }
}

public abstract class DoorManufacturer
{
    public void CreateDoor()
    {
    Door door = this.GetDoor();
    door.Design();
    door.Build();
    door.Coloring();
    Console.WriteLine("Your door is ready!");
    }

    public abstract Door GetDoor();
}

public class Carpenter : DoorManufacturer
{
    public override Door GetDoor()
    {
        return new WoodenDoor();
    }
}

Finally, we go to the carpenter to make the door and give the desired dimensions and features and ask them to make the door for us:
最后,我们去找木匠制作门,给出所需的尺寸和功能,并要求他们为我们制作门。

DoorManufacturer manufacturer = new Carpenter();
manufacturer.CreateDoor();

In the preceding design, if we need to add an iron door manufacturer, it is enough to consider a class for an iron door (Let us call it, IronDoor) which inherits from the Door class, and consider a class for an iron door manufacturer (Let us call it, IronDoorMaker) that inherits from DoorManufacturer. In this way, new doors and manufacturers can be defined without manipulating the previous codes. Finally, we will have the following class diagram for this design pattern:

在前面的设计中,如果我们需要添加一个铁门制造商,那么考虑一个继承自 Door 类的铁门类(我们称之为 IronDoor)就足够了,并考虑一个继承自 DoorManufacturer 的铁门制造商的类(我们称之为 IronDoorMaker)。通过这种方式,可以在不纵先前代码的情况下定义新的门和制造商。最后,我们将为这个设计模式提供以下类图:

alt text
Figure 2.2: Factory Method UML diagram
图 2.2: 工厂方法 UML 图

Participants:
参与者:

  • Product: Based on the preceding section, the Door is responsible for defining a template for the objects made by the factory method.
    Product:根据上一节,Door 负责为工厂方法生成的对象定义模板。

  • Concrete product: In the preceding scenario, WoodenDoor and IronDoor are responsible for implementing the template defined by the Product.
    具体产品:在前面的场景中,WoodenDoor 和 IronDoor 负责实现 Product 定义的模板。

  • Creator: This is also called the factory. In the preceding scenario, the DoorManufacturer is responsible for defining the Factory Method. This method is responsible for creating and returning a product-type object. In the preceding scenario, the GetDoor method is the same as the factory method. It is unnecessary to define this method as an abstract method, and the creator class can also have a default implementation of this method.
    创建者:这也称为工厂。在前面的方案中,DoorManufacturer 负责定义 Factory Method。该方法负责创建并返回 product-type 对象。在前面的方案中,GetDoor 方法与工厂方法相同。没有必要将此方法定义为抽象方法,并且 creator 类也可以具有此方法的默认实现。

  • Concrete creator: In the preceding scenario, the Carpenter and IronDoorMaker are responsible for rewriting the factory method to make the appropriate object and provide it to the creator.
    具体创建者:在前面的场景中,Carpenter 和 IronDoorMaker 负责重写工厂方法以生成适当的对象并将其提供给创建者。

Notes:
笔记:

  • In the given implementation and design (Figure 2.2), we can use the interface instead of the abstract class for the product.
    在给定的实现和设计(图 2.2)中,我们可以使用接口而不是产品的抽象类。

  • To reuse the created objects, you can use the object pool.
    This design pattern tries to manage the way objects are made, and this is because the factory method design pattern is included in the category of creational design patterns.
    要重用创建的对象,您可以使用对象池。此设计模式试图管理对象的创建方式,这是因为工厂方法设计模式包含在创建设计模式的类别中。

  • You can also use the Parametrized Factory Method to implement the Factory Method. In this implementation method, we send the type of object we want to create to the Factory Method through a parameter, and through that, we proceed to create the desired object. Concrete Creators can extend these methods by overriding them by using this implementation method. To implement this procedure, the GetDoor method in the preceding scenario is changed as follows:
    您还可以使用 Parametrized Factory Method 来实现 Factory Method。在这个实现方法中,我们通过参数将要创建的对象类型发送到 Factory Method,然后通过该参数,我们继续创建所需的对象。Concrete Creators 可以通过使用此实现方法覆盖这些方法来扩展这些方法。为了实现此过程,上述方案中的 GetDoor 方法更改如下:

    public virtual Door GetDoor(string type)
    {
    switch (type)
    {
        case "Wooden":
        return new WoodenDoor();
        case "Iron":
        return new IronDoor();
    }
    return null;
    }

In this case, if we want to add the aluminum door manufacturer, we can cover this new requirement by overriding the following method:
在这种情况下,如果我们想添加铝门制造商,我们可以通过覆盖以下方法来满足这个新要求:

public class AluminumDoorMaker: DoorManufacturer
{
    public override Door GetDoor(string type)
    {
    if (type == "Aluminum")
    return new AluminumDoor();
    else
    return base.GetDoor(type);
    }
}

C# language supports generics from version 2 onwards. This feature of the C# language, instead of creating many subclasses, you can create a product by simply providing a generic type. Using this feature, you can rewrite the concrete creator section as follows:
C# 语言支持版本 2 及更高版本的泛型。C# 语言的这一功能,无需创建许多子类,只需提供泛型类型即可创建产品。使用此功能,您可以按如下方式重写 concrete creator 部分:

public class StarndardDoorManufacturer<T>: DoorManufacturer
    where T: Door, new()
    {
        public override Door GetDoor()
        {
        return new T();
    }

}

Pay attention to the where section. With the help of this section, we can filter the generic types so that only types can be sent that are both Door type and its subset types (where T: Door) and also have a default constructor (where T: new()).

注意 where 部分。在本节的帮助下,我们可以过滤泛型类型,以便只能发送既是 Door 类型又是其子集类型(其中 T: Door)并且还具有默认构造函数(其中 T: new())的类型。

Finally, with the applied changes, it can be used as follows:
最后,通过应用的更改,可以按如下方式使用:

DoorManufacturer obj = new StarndardDoorManufacturer<WoodenDoor>();

obj.CreateDoor();

It is better to use a suitable naming template in design patterns so that the design pattern is understood by the template used. It would have been better in the preceding scenario, instead of using the title DoorManufacturer, titles such as DoorFactory or similar were used.

最好在设计模式中使用合适的命名模板,以便所使用的模板能够理解设计模式。在前面的场景中,最好不要使用标题 DoorManufacturer,而是使用 DoorFactory 或类似标题。

You can start the design with the factory method, then mature the design with the Abstract factory, prototype, or builder.

您可以从 factory 方法开始设计,然后使用 Abstract factory、prototype 或 builder 完善设计。

Consequences: Advantages
后果:优势

A loose connection between the creator and the product increases the ability to maintain and develop the code:

创建者和产品之间的松散连接增加了维护和开发代码的能力:

The principle of Single Responsibility Principal (SRP) has been observed. By moving the creation of the object outside the factory method, we made each method responsible for doing only one thing.
遵守单一责任委托人 (SRP) 的原则。通过将对象的创建移到工厂方法之外,我们使每个方法只负责做一件事。

The principle of Open/Close Principle (OCP) has been observed. As mentioned in the preceding scenario, new products and creators can be added to the program without manipulating the existing codes.
已遵守开/关原则 (OCP) 原则。如前面的场景所述,可以将新产品和创作者添加到程序中,而无需作现有代码。

Consequences: Disadvantages
后果:缺点

If the generic feature is not used, then each concrete product and creator must be defined as a subclass. This structure can become complicated to maintain with the increase in the number of classes.
如果未使用通用特征,则必须将每个具体产品和创建者定义为一个子类。随着类数量的增加,维护此结构可能会变得复杂。

Applicability:
适用性:

  • When a class does not know the exact type of objects it should create, and need to delegate this task to its child classes.
    当一个类不知道它应该创建的对象的确切类型,并且需要将此任务委托给其子类时。

  • When we are developing a software development framework and providing the ability to add new features to the framework.
    当我们开发软件开发框架并提供向框架添加新功能的能力时。

  • When we need to control the number of created objects and reuse created objects as much as possible.
    当我们需要控制已创建对象的数量并尽可能多地重用已创建的对象时。

Related patterns:
相关模式:

Some of the following design patterns are not related to the Factory Method design pattern, but to implement this design pattern, checking the following design patterns will be useful:
以下一些设计模式与 Factory Method 设计模式无关,但要实现此设计模式,检查以下设计模式将很有用:

  • Object pool 对象池
  • Abstract factory 抽象工厂
  • Template method 模板方法
  • Singleton 单例
  • Iterator 迭代器

Abstract factory

抽象工厂

The abstract factory design pattern is introduced and analyzed in this section, according to the structure presented in the GoF design patterns section in Chapter 1, Introduction to Design Patterns.
本节根据第 1 章 设计模式简介的 GoF 设计模式部分介绍的结构,介绍和分析抽象工厂设计模式。

Name:
Abstract factory
抽象工厂
Classification:
Creational design patterns
创建式设计模式
Also known as:
Kit
工具箱
Intent:
This design pattern tries to create a group of related objects through an interface without referring to their class.
此设计模式尝试通过接口创建一组相关对象,而不引用它们的类。

Motivation, Structure, Implementation, and Sample code:
动机、结构、实现和示例代码:
We have several car factories in the country that produce cars. The cars produced by these factories are divided into two categories: gasoline and diesel cars. So far, we are facing two types of abstractions in the scenario. The first abstraction is related to the car manufacturing plant, and the second is related to the type of car. So far in the scenario, we are facing the following class diagram:
我们在该国有几家生产汽车的汽车工厂。这些工厂生产的汽车分为汽油车和柴油车两大类。到目前为止,我们在场景中面临两种类型的抽象。第一个抽象与汽车制造厂有关,第二个抽象与汽车类型有关。到目前为止,在该方案中,我们面对的是以下类图:

alt text

Figure 2.3: Car-Car factory initial relation
图 2.3: 汽车-汽车工厂的初始关系

According to Figure 2.3, the following code could be considered:
根据图 2.3,可以考虑以下代码:

public abstract class CarFactory
{
    public abstract PetrolCar CreatePetrolCar();
    public abstract DieselCar CreateDieselCar();
}

public abstract class PetrolCar
{
    public abstract void AssembleSeats();
}

public abstract class DieselCar
{
    public abstract void AssembleDieselEngine();
}

According to the class (Figure 2.3 and the preceding code), we must bring classes that use these abstractions to complete the design. For example, the CreatePetrolCar method is in the CarFactory abstract class, but it is unclear how this method is implemented. Logically, it is also true because we still need to know which car manufacturer we are talking about. When it is clear which car manufacturer we are talking about, we can determine how CreatePetrolCar should be implemented. According to the presented scenario, we assume that we have two automobile factories in the country named IranKhodro and Saipa. These factories should be able to produce their own gasoline and diesel products. With this explanation, the preceding design changes as the following:

根据类(图 2.3 和前面的代码),我们必须引入使用这些抽象的类来完成设计。例如,CreatePetrolCar 方法位于 CarFactory 抽象类中,但尚不清楚此方法是如何实现的。从逻辑上讲,这也是正确的,因为我们仍然需要知道我们正在谈论的是哪个汽车制造商。当清楚我们谈论的是哪个汽车制造商时,我们可以确定应该如何实施 CreatePetrolCar。根据所呈现的情景,我们假设我们在该国有两家名为 IranKhodro 和 Saipa 的汽车工厂。这些工厂应该能够生产自己的汽油和柴油产品。通过此说明,前面的设计将更改如下:

alt text
Figure 2.4: IranKhodro-Saipa factories relations to CarFactory
图 2.4:IranKhodro-Saipa 工厂与 CarFactory 的关系

According to Figure 2.4, the following code could be considered:
根据图 2.4,可以考虑以下代码:

public abstract class CarFactory {
    public abstract PetrolCar CreatePetrolCar();
    public abstract DieselCar CreateDieselCar();
}

public class IranKhodro : CarFactory
{
    public override DieselCar CreateDieselCar()=> throw NotImplementedException();
    public override PetrolCar CreatePetrolCar() =>throw NotImplementedException();
}

public class Saipa : CarFactory
{
    public override DieselCar CreateDieselCar() =>throw NotImplementedException();
    public override PetrolCar CreatePetrolCar() =>throw NotImplementedException();
}

public abstract class PetrolCar
{
    public abstract void AssembleSeats();
}

public abstract class DieselCar
{
    public abstract void AssembleDieselEngine();
}

So far, it has been found that car factories are making gasoline and diesel cars. We accepted that Iran Khodro and Saipa are car factories, and they produce gasoline and diesel cars. Now the question remains unanswered here: which diesel car does the Iran Khodro or Saipa factory produce? To answer this question, we must first answer which car is gasoline and which is diesel. We can determine which car manufacturer produces gasoline and diesel cars when we answer this question. So, with these conditions, the class diagram changes as the following:

到目前为止,已经发现汽车工厂正在生产汽油和柴油汽车。我们接受了伊朗 Khodro 和 Saipa 是汽车工厂,他们生产汽油和柴油汽车。现在问题仍未得到解答:伊朗 Khodro 或 Saipa 工厂生产哪种柴油车?要回答这个问题,我们首先要回答哪辆车是汽油车,哪辆车是柴油车。当我们回答这个问题时,我们可以确定哪个汽车制造商生产汽油和柴油汽车。因此,在这些条件下,类图将更改如下:

alt text
Figure 2.5: Factories and cars relations

According to Figure 2.5, the following code could be considered:
根据图 2.5,可以考虑以下代码:

public abstract class CarFactory
{
    public abstract PetrolCar CreatePetrolCar();
    public abstract DieselCar CreateDieselCar();
}

public class IranKhodro : CarFactory
{
    public override DieselCar CreateDieselCar() => new Arena();
    public override PetrolCar CreatePetrolCar() => new Peugeot206();
}

public class Saipa : CarFactory
{
    public override DieselCar CreateDieselCar() => new Foton();
    public override PetrolCar CreatePetrolCar() => new Pride();
}

public abstract class PetrolCar
{
    public abstract void AssembleSeats();
}

public class Pride : PetrolCar
{
    public override void AssembleSeats()=>
    Console.WriteLine("Pride seats assembled");
}

public class Peugeot206 : PetrolCar
{
    public override void AssembleSeats() =>
    Console.WriteLine("Peugeot206 seats assembled");
}

public abstract class DieselCar
{
    public abstract void AssembleDieselEngine();
}

public class Foton : DieselCar
{
    public override void AssembleDieselEngine()=>
    Console.WriteLine("Foton engine assembled");
}

public class Arena : DieselCar
{
    public override void AssembleDieselEngine()=>
    Console.WriteLine("Arena engine assembled");
}

Now, it is clear which car manufacturers produce which cars and how each of these is produced. The design is almost finished, and we only need a user to build the desired car using our defined abstractions. So, with these conditions, the class diagram changes as follows:

现在,哪些汽车制造商生产哪些汽车以及每辆汽车是如何生产的,这一点就很清楚了。设计几乎完成,我们只需要一个用户使用我们定义的抽象来构建所需的汽车。因此,在这些条件下,类图将发生如下变化:

alt text
Figure 2.6: Abstract Factory UML diagram
图 2.6: 抽象工厂 UML 图

According to Figure 2.6, the following code could be considered:
根据图 2.6,可以考虑以下代码:

public class Client
{
    private readonly CarFactory factory;
    public Client(CarFactory factory) => this.factory = factory;

    public void CreatePetrolCar()
    {
        var petrol = factory.CreatePetrolCar();
        petrol.AssembleSeats();
    }
        public void CreateDieselCar()
    {
        var diesel = factory.CreateDieselCar();
        diesel.AssembleDieselEngine();
    }
}

As it is clear in the preceding code, the Client receives the car factory in the constructor and creates the cars using CreatePetrolCar and CreateDieselCar methods.

如前面的代码中所示,客户端在构造函数中接收汽车工厂,并使用 CreatePetrolCar 和 CreateDieselCar 方法创建汽车。

Participants:
参与者:

  • AbstractFactory: In the preceding scenario, the CarFactory has the task of defining a template for tasks whose purpose is to create an AbstractProduct.
    AbstractFactory:在前面的场景中,CarFactory 的任务是为创建 AbstractProduct 的任务定义模板。

  • ConcreteFactory: In the preceding scenario, it is IranKhodro and Saipa which has the task of implementing that aim to create a ConcreteProduct.
    ConcreteFactory:在前面的场景中,IranKhodro 和 Saipa 的任务是实现创建 ConcreteProduct 的目标。

  • AbstractProduct: In the preceding scenario, PetrolCar and DieselCar are responsible for defining the format for all kinds of products.
    AbstractProduct:在前面的场景中,PetrolCar 和 DieselCar 负责定义各种产品的格式。

  • ConcreteProduct: In the preceding scenario, it is Pride, Peugeot206, Arena, and Foton which is responsible for defining the product.
    ConcreteProduct:在前面的场景中,是 Pride、Peugeot206、Arena 和 Foton 负责定义产品。

  • Client: This is also called Client, in the preceding scenario, is responsible for using abstractions.
    Client:这也称为 Client,在前面的场景中,它负责使用抽象。

Notes:
笔记:

  • In the preceding design, instead of the abstract class for CarFactory, PetrolCar, and DieselCar, we can also use interfaces.
    在前面的设计中,我们还可以使用接口,而不是 CarFactory、PetrolCar 和 DieselCar 的抽象类。

  • Usually, the classes in this design pattern can be implemented with the factory method or Prototype design pattern.
    通常,此设计模式中的类可以使用工厂方法或 Prototype 设计模式实现。

  • If the only purpose is to hide the sampling method, then an abstract factory can be considered an alternative to a Facade.
    如果唯一目的是隐藏采样方法,那么抽象工厂可以被视为 Facade 的替代方案。

  • The difference between the builder design pattern and abstract factory is that the Builder design pattern can be used to create complex objects. Also, it is possible to do additional tasks along with the sampling process when using the Builder design pattern. Still, the created sample is returned immediately in the abstract factory design pattern.
    Builder 设计模式和抽象工厂之间的区别在于 Builder 设计模式可用于创建复杂对象。此外,在使用 Builder 设计模式时,可以在采样过程中执行其他任务。尽管如此,创建的示例仍会立即以抽象的 Factory 设计模式返回。

  • Abstract factory design patterns can also be implemented as Singleton.
    抽象工厂设计模式也可以作为 Singleton 实现。

  • All products of the same family are implemented and integrated into a concrete factory related to that family.
    同一系列的所有产品都被实施并集成到与该系列相关的混凝土工厂中。

  • In C# language, to implement this design pattern, Generics can be used to design Factory and Product.
    在 C# 语言中,为了实现此设计模式,可以使用泛型来设计 Factory 和 Product。

Consequences: Advantages
后果:优势

  • There is always a guarantee that the products received from the factory are of the same family.
    始终保证从工厂收到的产品属于同一系列。

  • There is a loose connection between the Client and concrete products.
    客户和混凝土产品之间存在松散的联系。

  • The principle of SRP has been observed.
    已遵守 SRP 的原则。

  • The principle of OCP has been observed. New products can be defined without changing the existing codes.
    OCP 的原理已被遵守。可以在不更改现有代码的情况下定义新产品。

Consequences: Disadvantages
后果:缺点

  • This structure can become complicated to maintain over time and with the increase in the number of classes.
    随着时间的推移和类数量的增加,维护这种结构可能会变得复杂。

  • With the addition of a new product, the abstract factory must be changed, which leads to the change of all concrete factories.
    随着新产品的加入,抽象工厂必须改变,这导致了所有具体工厂的改变。

Applicability:
适用性:

  • When dealing with a family of products and do not want to make the code dependent on concrete products, we can use this pattern.
    当处理一系列产品并且不想使代码依赖于具体产品时,我们可以使用这种模式。

  • When faced with a class that consists of a collection of factory methods, the abstract factory pattern can be useful.
    当面对由工厂方法集合组成的类时,抽象工厂模式可能很有用。

Related patterns:
相关模式:
Some of the following design patterns are not related to abstract factory design patterns, but to implement this design pattern, checking the following design patterns will be useful:
以下一些设计模式与抽象工厂设计模式无关,但要实现此设计模式,检查以下设计模式将很有用:

  • Prototype 原型
  • Factory method 工厂方法
  • Singleton 单例
  • Facade 立面
  • Builder 生成器
  • Bridge Bridge

Builder

生成器模式

The builder design pattern is introduced and analyzed in this section, according to the structure presented in the GoF design patterns section in Chapter 1, Introduction to Design Pattern.

本节根据第 1 章 设计模式简介的 GoF 设计模式部分介绍的结构,介绍和分析了构建器设计模式。

Name:
Builder
生成器模式
Classification:
Creational design patterns
创建式设计模式
Also known as:
-
Intent:
意图:
This design pattern tries to create complex objects according to the requirement.
此设计模式尝试根据要求创建复杂对象。
Motivation, Structure, Implementation, and Sample code:
动机、结构、实现和示例代码:
Suppose we are building a residential unit. Residential units can have different construction processes according to the use case. A residential unit can be a villa or an apartment, which can be concrete, iron, or prefabricated. To model the residential unit, there are different ways. For example, we can define a parent class for a residential unit and connect different types of residential units to each other using the parent-child relationship. Although this method solves the design problem, in the end, it causes us to face many parent-child relationships that threaten the readability and maintainability of the code.

假设我们正在建造一个住宅单元。根据用例,住宅单元可以有不同的施工流程。住宅单元可以是别墅或公寓,可以是混凝土、铁或预制的。要对住宅单元进行建模,有多种方法。例如,我们可以为住宅单元定义一个父类,并使用父子关系将不同类型的住宅单元相互连接。虽然这种方法解决了设计问题,但最终还是导致我们面临很多父子关系,威胁到代码的可读性和可维护性。

We can use a class to define the residential unit and send all the necessary parameters to define the residential unit through the constructor, in the form of optional parameters, to the class. Although this method solves the design problem, too, it obviously causes the emergence of strange constructors with all the input parameters, which are not given a value when many of them are used.
我们可以使用一个类来定义住宅单元,并通过构造函数以可选参数的形式将定义住宅单元所需的所有参数发送到类。虽然这种方法也解决了设计问题,但它显然会导致出现具有所有输入参数的奇怪构造函数,当使用许多参数时,这些参数没有被赋予值。

For example, we come across the codes like the following:
例如,我们遇到了如下代码:

House house1 = new House(p1,p2,null,null,null,null,null,p3,null,null);

House house2 = new House(null,null,null,null,null,p4,null,null,null,null);

We can put different constructors in the definition of a class, for the residential unit, for different states. Although this method solves the problem, it causes the appearance of telescopic constructors and makes it difficult to maintain the class:
我们可以在类的定义中为住宅单元和不同的状态放置不同的构造函数。虽然这种方法解决了这个问题,但它会导致伸缩构造器的出现,并且很难维护类:

public class House{
Public House(string p1){}
Public House(string p1, string p2){}
Public House(string p1, string p2, string p3){}
Public House(string p1, string p2, string p3, string p4){}
// …
}

The builder design pattern tries to solve these problems. In the preceding example, this design pattern helps to define the stages and steps of building a residential unit so that it is possible to build a residential unit using these stages and steps. For example, separate the wall construction process from the door construction process and present each one separately.

构建器设计模式尝试解决这些问题。在前面的示例中,此设计模式有助于定义构建住宅单元的阶段和步骤,以便可以使用这些阶段和步骤构建住宅单元。例如,将墙构造过程与门构造过程分开,并分别呈现每个过程。

To make the matter clear, let us pay attention to another scenario in this regard:

为了弄清楚这个问题,让我们注意这方面的另一种情况:

A requirement is raised, and it is requested to provide the ability to make a mobile phone. Based on this requirement, we notice that mobile phones have different models. For example, we consider Samsung and Apple mobile phones. We understand that regardless of the manufacturing company, every mobile phone needs to be manufactured for the screen, body, camera, and so on, and the appropriate operating system is installed on the mobile phone. It is also clear that the manufacturing methods of both Samsung and Apple companies are completely different for each of these parts.

提出了一个要求,并要求提供制作手机的能力。根据这个要求,我们注意到移动电话的型号不同。例如,我们考虑三星和苹果手机。我们明白,无论制造公司是谁,每部手机都需要针对屏幕、机身、相机等进行制造,并在手机上安装适当的作系统。同样明显的是,三星和苹果公司的制造方法对于这些部件中的每一个都完全不同。

With these explanations, we are faced with two different elements in the design:
通过这些解释,我们在设计中面临着两个不同的元素:

  • The first element: The product we want to produce
    第一个元素:我们要生产的产品

  • The second element: The method of making the product
    第二个元素:制作产品的方法

For the first element, we can define two classes for Samsung and Apple, and for the second element, we can use an interface to format the steps of making a mobile phone. With these explanations, we can consider the following class figure:

对于第一个元素,我们可以为 Samsung 和 Apple 定义两个类,对于第二个元素,我们可以使用接口来格式化制作手机的步骤。通过这些解释,我们可以考虑以下类图:

alt text
Figure 2.7: Cellphone construction initial relations
图 2.7: 手机构造初始关系

According to Figure 2.7, the following code could be considered:
根据图 2.7,可以考虑以下代码:

public class CellPhone
{
    public string CameraResolution { get; set; }
    public string MonitorSize { get; set; }
    public string BodyMaterial { get; set; }
    public string OSName { get; set; }
}

public interface ICellPhoneBuilder
{
    public CellPhone Phone { get; }
    void BuildMonitor();
    void BuildBody();
    void BuildCamera();
    void PrepareOS();
}

public class Samsung : ICellPhoneBuilder
{
    public CellPhone Phone { get; private set; }
    public Samsung() => Phone = new CellPhone();
    public void BuildBody() => Phone.BodyMaterial = "Titanium";
    public void BuildCamera() => Phone.CameraResolution = "10 MP";
    public void BuildMonitor() => Phone.MonitorSize = "10 Inch";
    public void PrepareOS() => Phone.OSName = "iOS";
}

public class Apple : ICellPhoneBuilder
{
    public CellPhone Phone { get; private set; }
    public Apple() => Phone = new CellPhone();
    public void BuildBody() => Phone.BodyMaterial = "Aluminum";
    public void BuildCamera() => Phone.CameraResolution = "12 MP";
    public void BuildMonitor() => Phone.MonitorSize = "9.8 Inch";
    public void PrepareOS() => Phone.OSName = "Android 10";
}

Up to this design point, we have been able to define and implement the necessary steps to make a mobile phone. Now, what is the process of making a mobile phone? How should the defined steps be combined with each other to finally make a mobile phone? To answer these questions, we use the CellPhoneDirector class and define the work process in its format. With these explanations, the class diagram changes as follows:

到目前为止,我们已经能够定义和实施制造手机的必要步骤。那么,制作手机的过程是怎样的呢?定义的步骤应该如何相互结合,最终制作出手机呢?为了回答这些问题,我们使用 CellPhoneDirector 类并按其格式定义工作流程。通过这些说明,类图将更改如下:

alt text
Figure 2.8: Builder design pattern UML diagram
图 2.8.. Builder 设计模式 UML 图

According to Figure 2.8, the following code could be considered:
根据图 2.8,可以考虑以下代码:

public class CellPhoneDirector
{
private ICellPhoneBuilder builder;

public CellPhoneDirector(ICellPhoneBuilder builder) => this.builder = builder;

public CellPhone Construct()
{
    builder.BuildBody();
    builder.BuildMonitor();
    builder.BuildCamera();
    builder.PrepareOS();
    return builder.Phone;
}

}

According to the preceding code, it is clear that to build a mobile phone (Construct), its body should be built first (BuildBody), then the screen (BuildMonitor), and then the camera should be built (BuildCamera), and finally, the operating system should be prepared (PrepareOS). With these explanations, the design is finished, and to use this structure, you can use the following code:

根据前面的代码,很显然,要构建一部手机(Construct),首先要构建它的机身(BuildBody),然后构建屏幕(BuildMonitor),然后构建摄像头(BuildCamera),最后准备作系统(PrepareOS)。通过这些说明,设计就完成了,要使用此结构,您可以使用以下代码:

CellPhoneDirector director = new CellPhoneDirector(new Samsung());

var phone = director.Construct();
Console.WriteLine($"
    Body: {phone.BodyMaterial},
    Camera: {phone.CameraResolution},
    Monitor: {phone.MonitorSize},
    OS: {phone.OSName}"
);

Participants:
参与者:

  • Builder: In the preceding scenario, it is ICellPhoneBuilder, which is responsible for defining the format of the steps to build an object. In other words, it has the task of determining what stages and steps should be defined to build an object.
    Builder:在前面的场景中,它是 ICellPhoneBuilder,它负责定义构建对象的步骤的格式。换句话说,它的任务是确定应该定义哪些阶段和步骤来构建对象。

  • ConcreteBuilder: In the preceding scenario, it is Apple and Samsung that are responsible for implementing the steps announced by the builder and specifying how each step should be implemented.
    ConcreteBuilder:在前面的场景中,Apple 和 Samsung 负责实施生成器宣布的步骤,并指定每个步骤的实施方式。

  • Director: In the preceding scenario, it is CellPhoneDirector, which is responsible for implementing the object creation process. During the implementation of this process, the director uses the steps declared by the builder and produces the object.
    Director:在前面的场景中,是 CellPhoneDirector,它负责实现对象创建过程。在此过程的实施过程中,director 使用生成器声明的步骤并生成对象。

  • Product: In the preceding scenario, it is the CellPhone that is responsible for defining the complex object that we are trying to build. In fact, the product is built by the Director within ConceretBuilders through the steps defined in the Builder.
    Product:在前面的场景中,CellPhone 负责定义我们尝试构建的复杂对象。事实上,该产品是由 ConceretBuilders 中的 Director 通过 Builder 中定义的步骤构建的。

Notes:
笔记:

  • An abstract class can also be used to define Builder.
    抽象类也可用于定义 Builder。

  • Using this design pattern, the details of the object construction are hidden from the user's view. If we need to create an object differently, all we need to do is define a new builder.
    使用此设计模式,对象构造的细节对用户来说是隐藏的。如果我们需要以不同的方式创建对象,我们需要做的就是定义一个新的构建器。

  • Using this design pattern, and the director's use, there is more control over the object creation process.The Builder design pattern can also be implemented as a Singleton.
    使用此设计模式以及 director 的使用,可以更好地控制对象创建过程。Builder 设计模式也可以作为 Singleton 实现。

  • Builder and bridge design patterns can be combined. In this case, the director plays the role of abstraction, and various builders play the role of implementation.
    Builder 和 Bridge 设计模式可以组合使用。在这种情况下,director 扮演抽象的角色,而各种 builder 扮演 implementation 的角色。

Consequences: Advantages
后果:优势

  • The step-by-step construction of the object allows for better control over the construction process.
    对象的逐步构造可以更好地控制构造过程。

  • Single Responsibility Principle (SRP) is met. The code related to the construction of a complex object is separated from the business logic.
    满足单一责任原则 (SRP)。与复杂对象构造相关的代码与业务逻辑分离。

Consequences: Disadvantages
后果:缺点

  • With the increase in the number of concrete builders, the complexity of the code increases due to the increase in the volume of coding.
    随着混凝土构建器数量的增加,由于编码量的增加,代码的复杂性也随之增加。

Applicability:
适用性:

  • When we have telescopic constructors in our classes, this design pattern will likely improve the quality of code and design.
    当我们的类中有伸缩构造器时,这种设计模式可能会提高代码和设计的质量。

  • The product manufacturing has the same steps, but the output can be different, then this design pattern will be useful. For example, in the preceding scenario, creating the product in question had the same steps, but the output was different (the Apple company had its own output, and the Samsung company had its own output).
    产品制造具有相同的步骤,但输出可能不同,那么这种设计模式将很有用。例如,在前面的场景中,创建相关产品的步骤相同,但输出不同(Apple 公司有自己的输出,而 Samsung 公司有自己的输出)。

Related patterns:
Some of the following design patterns are not related to the Builder design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:
以下一些设计模式与 Builder 设计模式无关,但为了实现此设计模式,检查以下设计模式将非常有用:

  • Singleton
  • Composite
  • Bridge
  • Abstract Factory

Prototype

原型

In this section, the prototype design pattern is introduced and analyzed according to the structure presented in the GoF design patterns section in Chapter 1, Introduction to Design Patterns.
在本节中,根据第 1 章 设计模式简介的 GoF 设计模式部分中介绍的结构介绍和分析原型设计模式。

Name:
Prototype
原型
Classification:
Creational design patterns
创建式设计模式
Also Known As:
-
Intent:
意图:
This design pattern tries to create objects using a prototype.
此设计模式尝试使用原型创建对象。
Motivation, Structure, Implementation, and Sample code:
动机、结构、实现和示例代码:
Suppose that we have a clothing factory, and we intend to produce different types of pants. The usual method is that the pants are designed first. Then, a pair of pants is produced as a sample based on this design. Finally, the rest of the pants are mass-produced. Pants that are designed and produced at the beginning are called prototypes. According to this scenario, it is very important to define a method to mass-produce the rest of the pants from the prototype.
假设我们有一家服装厂,我们打算生产不同类型的裤子。通常的方法是先设计裤子。然后,根据此设计生产一条裤子作为样品。最后,其余的裤子都进行了批量生产。一开始设计和生产的裤子称为原型。根据这个场景,定义一种方法来从原型中批量生产其余裤子是非常重要的。

There are different types of pants, such as jeans, linen, and cloth pants. The production of each prototype is different from the other. But in the meantime, having a method for mass production of any pants (copying) is common among different types of pants. According to this scenario, we face the following class diagram:
有不同类型的裤子,例如牛仔裤、亚麻布和布裤。每个原型的生产都不同。但与此同时,拥有一种大规模生产任何裤子(复制)的方法在不同类型的裤子中很常见。根据此方案,我们面对以下类图:

alt text
Figure 2.9: Pants initial relation

According to Figure 2.9, the following code could be considered:
根据图 2.9,可以考虑以下代码:

public interface IPant
{
    IPant Clone();
}

public class FabricPant : IPant
{
    public IPant Clone() => this.MemberwiseClone() as IPant;
}

public class CottonPant : IPant
{
    public IPant Clone() => this.MemberwiseClone() as IPant;
}

public class JeanPant : IPant
{
    public IPant Clone() => this.MemberwiseClone() as IPant;
}

According to the preceding structure, to copy the pants, the clone method is defined in the IPant interface, and each class copies the object using the MemberwiseClone method.

根据前面的结构,要复制 pants,在 IPant 接口中定义了 clone 方法,每个类都使用 MemberwiseClone 方法复制对象。

Now that the design is finished, the following structure can be used:
现在设计已完成,可以使用以下结构:

IPant jean1 = new JeanPant();

IPant jean2 = jean1.Clone();

The point in the preceding code is the method of copying the object. The objects are copied using the shallow method. In shallow copying, value types are copied bit by bit, but in the case of reference types, only the address is copied, and not the object, which causes another object to be affected by changing the value. To prevent this from happening, the deep method should be used to copy objects. Refer to the following figure:
上述代码中的要点是复制对象的方法。使用 shallow 方法复制对象。在浅层复制中,值类型是逐位复制的,但在引用类型的情况下,只复制地址,而不复制对象,这会导致另一个对象受到更改值的影响。为了防止这种情况发生,应该使用 deep 方法来复制对象。参考下图:

alt text
Figure 2.10: Shallow Copy
图 2.10.. 浅拷贝

The preceding figure shows the shallow copy, in which object X1 refers to B1 and object B1 also refers to C1. After copying, a new object named X2 is created, which still refers to B1. Therefore, applying a change in B1 through X1 will cause this change to be felt by object X2 as well:
上图显示了浅拷贝,其中对象 X1 引用 B1,对象 B1 也引用 C1。复制后,将创建一个名为 X2 的新对象,该对象仍引用 B1。因此,在 B1 到 X1 中应用更改将导致对象 X2 也能感受到此更改:

alt text
Figure 2.11: Deep Copy

The preceding figure also shows the copy by deep method, in which after the creation of object X2, new objects B2 and C2 are also created, and X2 refers to new objects B2 and C2. Therefore, applying changes in B1 through X1 will not cause these changes to be felt in object X2.

上图还显示了 deep 复制方法,其中在创建对象 X2 之后,还会创建新的对象 B2 和 C2,X2 引用新的对象 B2 和 C2。因此,在 B1 到 X1 中应用更改不会导致在对象 X2 中感觉到这些更改。

According to the preceding explanations, and to clarify the differences between the shallow and deep methods, we also define another method called DeepClone. There are different methods to implement deep copy. Here we use the following method. Also, we define a class called Cloth, and we define the characteristics of the fabric through this class. The following code can be considered:

根据前面的解释,为了明确 shallow 和 deep 方法之间的区别,我们还定义了另一种名为 DeepClone 的方法。有多种方法可以实现 Deep Copy。这里我们使用以下方法。此外,我们定义了一个名为 Cloth 的类,我们通过这个类来定义织物的特性。可以考虑以下代码:

public class Cloth
{
    public string Color { get; set; }
}

public interface IPant
{
    public int Price { get; set; }
    public Cloth ClothInfo { get; set; }

    IPant Clone();
    IPant DeepClone();
}

public class JeanPant : IPant
{
    public int Price { get; set; }
    public Cloth ClothInfo { get; set; }

    public IPant Clone() => this.MemberwiseClone() as IPant;

    public IPant DeepClone()
    {
    JeanPant pant = this.MemberwiseClone() as JeanPant;
    pant.ClothInfo = new Cloth() { Color = this.ClothInfo.Color };
    return pant;
    }

    public override string ToString() =>
    $"Color:{this.ClothInfo.Color}, Price: {this.Price}";
}

Now, to check the output of each method, we will test the copy methods using the following code. For Shallow copying, we have:
现在,为了检查每个方法的输出,我们将使用以下代码测试 copy 方法。对于浅拷贝,我们有:

IPant jean1 = new JeanPant()
{
    Price = 10000,
    ClothInfo = new Cloth(){ Color = "Red"}
};

IPant jean2 = jean1.Clone() ;//Shallow Copy
jean2.Price = 11000;
jean2.ClothInfo.Color = "Geen";
Console.WriteLine($"jean1: {jean1}");

Console.WriteLine($"jean2: {jean2}");

By running the preceding code, we get the following output:
通过运行上述代码,我们得到以下输出:

jean1: Color: Geen, Price: 10000
jean2: Color: Geen, Price: 11000

In this copy method, for reference types, only the reference address is copied; changing the value in ClothInfo through jean2 causes the ClothInfo value in jean1 to change as well.
在此 copy 方法中,对于引用类型,仅复制引用地址;通过 jean2 更改 ClothInfo 中的值会导致 jean1 中的 ClothInfo 值也发生更改。

But if we use DeepClone instead of the clone method in the preceding code, then we will see the following output:
但是,如果我们在前面的代码中使用 DeepClone 而不是 clone 方法,那么我们将看到以下输出:

jean1: Color: Red, Price: 10000
jean2: Color: Geen, Price: 11000

Now, if we return to the proposed scenario, we can slightly change the design provided for the Prototype design pattern.
现在,如果我们返回到建议的场景,我们可以稍微更改为 Prototype 设计模式提供的设计。

In the current design,It is not yet clear how the user can communicate.
目前尚不清楚用户如何进行通信。

Also, in the current design, when the number of prototypes is not a fixed value, and the prototypes can be dynamically created and destroyed, no solution has been provided.

此外,在当前设计中,当原型的数量不是固定值,并且可以动态创建和销毁原型时,没有提供任何解决方案。

To answer the first requirement, the user communication to produce objects in the preceding scenario should be through IPant. For the second requirement, another class can be used as Registry, and the user's relationship with the prototype can be promoted through the registry as the following:
为了满足第一个要求,在前面的方案中生成对象的用户通信应通过 IPant 进行。对于第二个要求,可以使用另一个类作为 Registry,并且用户与原型的关系可以通过 Registry 进行提升,如下所示:

alt text
Figure 2.12: Prototype design pattern UML diagram
图 2.12: 原型设计模式 UML 图

According to Figure 2.12, the following code could be considered:
根据图 2.12,可以考虑以下代码:

public class PantRegistry
{
    public PantRegistry() => Pants = new List<IPant>();
    public List<IPant> Pants { get; private set; }
    public void Add(IPant obj) => Pants.Add(obj);
    public IPant GetByColor(string color) =>
        Pants
        .OrderBy(x => Guid.NewGuid())
        .FirstOrDefault(x => x.ClothInfo.Color == color)
        .DeepClone();

    public IPant GetByType(Type type) =>
        Pants
        .OrderBy(x => Guid.NewGuid())
        .FirstOrDefault(x => x.GetType() == type)
        .DeepClone();

}

In the preceding code, a feature called Pants is presented, which has the role of a reservoir, and objects are stored in this reservoir. The Add method is used to store the new object in the repository, and the GetByColor and GetByType methods are used to search the repository and find the desired object and copy it. To use this code, you can do the following:
在上面的代码中,提出了一个名为 Pants 的功能,它具有 reservoir 的角色,并且对象存储在此 reservoir 中。Add 方法用于将新对象存储在存储库中,GetByColor 和 GetByType 方法用于搜索存储库并查找所需对象并复制它。要使用此代码,您可以执行以下作:

IPant jean1 = new JeanPant()
{
    Price = 10000,
    ClothInfo = new Cloth() { Color = "Red" }
};

IPant cotton1 = new CottonPant()
{
    Price = 7000,
    ClothInfo = new Cloth() { Color = "Red" }
};

IPant fabric1 = new FabricPant()
{
    Price = 12000,
    ClothInfo = new Cloth() { Color = "Blue" }
};

PantRegistry registry = new PantRegistry();
registry.Add(jean1);
registry.Add(cotton1);
registry.Add(fabric1);
Console.WriteLine($"{jean1}");

// Get a pair of jeans
Console.WriteLine($"{registry.GetByType(typeof(JeanPant))}");

// Get a pair of red pants regardless of the type of pants
Console.WriteLine($"{registry.GetByColor("Red")}");

As you can see in the preceding code, the objects are added to the repository after being created, searched, and copied through different methods.

如前面的代码所示,对象在通过不同的方法创建、搜索和复制后会添加到存储库中。

Participants:
参与者:

  • Prototype: In the preceding scenario, it is IPant that is responsible for defining the template for copying the object
    原型:在前面的场景中,是 IPant 负责定义用于复制对象的

  • Concrete prototype: In the preceding scenario, it is CottonPant, JeanPant, and FabricPant, which is responsible for implementing the provided template for copying the object.
    模板具体原型:在前面的场景中,是 CottonPant、JeanPant 和 FabricPant 负责实现提供的用于复制对象的模板。

  • Prototype registry: which is also called Prototype Manager. In the preceding scenario, it is PantRegistry whose task is to facilitate access to objects.
    Prototype registry:也称为 Prototype Manager。在前面的场景中,PantRegistry 的任务是促进对对象的访问。

  • Client: It is the same user who sends a request to create a new object through Prototype or Prototype Registry.
    客户端:通过 Prototype 或 Prototype Registry 发送创建新对象的请求的同一用户。

Notes:

  • In .NET, the ICloneable interface can also be used as a prototype.
    在 .NET 中,ICloneable 接口也可以用作原型。

  • Singleton can also be used in the implementation of the prototype design pattern.
    Singleton 也可以用于原型设计模式的实现。

  • In the command design pattern, if you need to save history, you can use the prototype design pattern.
    在命令设计模式中,如果需要保存历史记录,可以使用 prototype 设计模式。

  • If there is a need to store the state of the object and this object:
    如果需要存储对象和此对象的 state:

    • It is not a complex object.
      它不是一个复杂对象。
    • Does not have complex references or can be defined easily.
      没有复杂的引用或可以轻松定义。
    • Then the prototype design pattern can be used instead of the memento design pattern.
      然后,可以使用 prototype 设计模式来代替 memento 设计模式。
  • Prototype registry can also be implemented using generics:
    原型注册表也可以使用泛型实现:

    public interface IPrototypeRegistry
    {
    void Add<T>(T obj) where T : ICloneable;
    T Get<T>() where T : ICloneable;
    }
  • Consequences: Advantages
    后果:优势

  • Repetitive codes for creating objects are removed, and we are faced with a smaller amount of code.
    用于创建对象的重复代码被删除,我们面临的代码量较小。

  • Due to the presence of the copying method, making complex objects can be easy.
    由于存在复制方法,制作复杂对象很容易。

  • Objects can be copied, and a new object can be created without the need for a concrete class.
    可以复制对象,并且可以在不需要具体类的情况下创建新对象。

  • Consequences: Disadvantages
    结果:缺点

  • The process of copying objects can be complicated.
    复制对象的过程可能很复杂。

  • Implementing the copy process for classes that have circular dependencies can be complicated.
    为具有循环依赖关系的类实现复制过程可能很复杂。

Applicability:
适用性:

  • When the type of object to be created is determined at runtime. For example, Dynamic Loading
    在运行时确定要创建的对象的类型时。例如,Dynamic Loading
  • When the objects of a class have almost the same data content
    当一个类的对象具有几乎相同的数据内容时

Related patterns:
相关模式:
Some of the following design patterns are not related to prototype design patterns, but to implement this design pattern, checking the following design patterns will be useful:
以下一些设计模式与原型设计模式无关,但要实现此设计模式,检查以下设计模式将很有用:

  • Singleton
  • Command
  • Memento
  • Abstract factory

Singleton

单例

In this section, the singleton design pattern is introduced and analyzed according to the structure presented in the GoF design patterns section in Chapter 1, Introduction to Design Pattern.
在本节中,根据第 1 章 设计模式简介的 GoF 设计模式部分中介绍的结构介绍和分析了单例设计模式。

Name:
Singleton
单例
Classification:
Creational design patterns
创建式设计模式
Also known as:
-
Intent:
This design pattern tries to provide a structure in which there is always an object of the class. One of the most important reasons for having only one object of a class is to control access to common resources such as databases and the like.
此设计模式尝试提供一个结构,其中始终存在类的对象。只有一个类对象的最重要原因之一是控制对公共资源(如数据库等)的访问。
Motivation, Structure, Implementation, and Sample code:
动机、结构、实现和示例代码:
Suppose we want to design the appropriate infrastructure for communication with the SQL Server database. For this purpose, we have created the DbConnectionManager class. During the program, to use this class, we need to always have only one object of it. In this scenario, the database is the shared resource. With these explanations, the following design can be imagined:
假设我们想要设计适当的基础结构来与 SQL Server 数据库进行通信。为此,我们创建了 DbConnectionManager 类。在程序过程中,要使用这个类,我们只需要只有一个对象。在此方案中,数据库是共享资源。通过这些解释,可以想象出以下设计:

alt text
Figure 2.13: Database connection manager initial relation
图 2.13.. 数据库连接管理器初始关系

According to Figure 2.13, the following code could be considered:
根据图 2.13,可以考虑以下代码:

public class DbConnectionManager
{
    private DbConnectionManager() { }
    public static DbConnectionManager GetInstance() => new();
}

As you can see in Figure 2.13 class diagram and preceding code, the constructor of the class is defined with a private access modifier. The reason for this is to remove access to the constructor from outside the class. Because when the constructor of the class is called, an object must be returned, which can ruin the design. Therefore, to prevent this from happening, access to the class’s constructor is limited. Within the class, the GetInstance method is defined as public static. So, it is possible to access this method outside the class without creating an object of the desired class type. In fact, this method will be responsible for presenting the object to the outside.

如图 2.13 类图和前面的代码所示,类的构造函数是使用 private 访问修饰符定义的。这样做的原因是从类外部删除对构造函数的访问。因为当调用类的构造函数时,必须返回一个对象,这可能会破坏设计。因此,为了防止这种情况发生,对类的构造函数的访问受到限制。在该类中,GetInstance 方法定义为 public static。因此,可以在类外部访问此方法,而无需创建所需类类型的对象。实际上,此方法将负责将对象呈现给外部。

The preceding code still does not cover the main requirement, which is to have only one object. Because every time the GetInstance method is called, a new object is always created and returned. The following code shows how to use this class and the existing problem:

前面的代码仍然没有涵盖主要要求,即只有一个对象。因为每次调用 GetInstance 方法时,总是会创建并返回一个新对象。下面的代码演示如何使用此类和现有问题:

DbConnectionManager obj1 = DbConnectionManager.GetInstance();

DbConnectionManager obj2 = DbConnectionManager.GetInstance();

Console.WriteLine($"obj1: {obj1.GetHashCode()}, obj2: {obj2.GetHashCode()}");

Output:
输出:

obj1: 58225482, obj2: 54267293

As shown in the preceding code and its corresponding output, two different objects have been created. To prevent this, we need to slightly change the GetInstance method:
如前面的代码及其相应的输出所示,已经创建了两个不同的对象。为了防止这种情况,我们需要稍微更改 GetInstance 方法:

  • The first step of the change is to return the object directly to the user after it is created and save it in a variable, and
    更改的第一步是在创建对象后直接将对象返回给用户,并将其保存在变量中,
  • The second step is to check before creating the object; if this variable contains the object, no more new objects will be created.
    第二步是在创建对象之前进行检查;如果此变量包含对象,则不会再创建新对象。
  • Therefore, the class diagram and code will change as the following:
    因此,类图和代码将更改如下:

alt text
Figure 2.14: Singleton design pattern UML diagram
图 2.14: 单例设计模式 UML 图

According to Figure 2.14, the following code could be considered:

根据图 2.14,可以考虑以下代码:

public class DbConnectionManager
{
    private static DbConnectionManager _instance;
    private DbConnectionManager() { }
    public static DbConnectionManager GetInstance()
    {
        if (_instance == null)
        _instance = new();
        return _instance;
    }
}

Now if we run these codes again, we have the following output:
现在,如果我们再次运行这些代码,我们将得到以下输出:

obj1: 58225482, obj2: 58225482

As you can see, only one object of DbConnectionManager class is created and available. This way of implementing the singleton design pattern is suitable for single and multi-thread environments. This implementation needs changes, and this design pattern should be implemented in a thread-safe manner. If this design pattern is implemented using the early initialization method, the problem raised for multi-thread environments will not exist, and the desired object will be created for each AppDomain.

如您所见,仅创建一个 DbConnectionManager 类的对象且可用。这种实现单例设计模式的方式适用于单线程和多线程环境。此实现需要更改,并且此设计模式应以线程安全的方式实现。如果此设计模式是使用早期初始化方法实现的,则为多线程环境引发的问题将不存在,并且将为每个 AppDomain 创建所需的对象。

Before dealing with the implementation of thread Safe, let us first observe the problem by simulating the multi-thread environment:
在处理线程 Safe 的实现之前,我们先通过模拟多线程环境来观察问题:

Parallel.Invoke(() =>
    {
    DbConnectionManager obj1 = DbConnectionManager.GetInstance();
    Console.WriteLine($"obj1: {obj1.GetHashCode()}");
    }, () =>
        {
        DbConnectionManager obj2 = DbConnectionManager.GetInstance();
        Console.WriteLine($"obj2: {obj2.GetHashCode()}");
        });

The Invoke method in the Parallel class tries to perform the provided tasks in parallel. By running the preceding code, we can see the output as follows:
Parallel 类中的 Invoke 方法尝试并行执行提供的任务。通过运行上述代码,我们可以看到如下输出:

obj2: 6044116

obj1: 27252167

Pay attention that these outputs may be different on different machines. The preceding output shows that the first obj2 is prepared, and then obj1 is prepared. Since these two objects have different HashCodes, we can conclude that even though the singleton pattern is used, in the multi-thread environment, instead of just one object, we are faced with several objects. Now, to solve this problem, as mentioned before, we need to implement Singleton as thread Safe. There are different ways to implement Singleton as thread Safe. We do this using the lock block. For this purpose, the DbConnectionManager class is changed as follows:

请注意,这些输出在不同计算机上可能有所不同。从上述输出可以看出,先准备了第一个 obj2,然后准备了 obj1。由于这两个对象具有不同的 HashCodes,我们可以得出结论,即使使用了单例模式,在多线程环境中,我们面对的不仅仅是一个对象,而是多个对象。现在,为了解决这个问题,如前所述,我们需要将 Singleton 实现为 thread Safe。有多种方法可以将 Singleton 实现为 thread Safe。我们使用 lock 块来做到这一点。为此,DbConnectionManager 类更改如下:

public class DbConnectionManager
{
    private static DbConnectionManager _instance;
    private static object locker = new();
    private DbConnectionManager() { }
    public static DbConnectionManager GetInstance()
    {
        lock (locker)
            {
                if (_instance == null)
                _instance = new();
                return _instance;
            }
    }
}

In the preceding code, by using the lock block, the first thread that launches the method takes over the locker resource and enters the lock block. The remaining threads that enter the method, because the locker resource is not available are stopped so that the previous thread releases the resource. Immediately after releasing the resource, the next thread receives the resource and enters the lock block. The problem of not being thread-safe is solved, but the code still can be improved. The important problem of this implementation method is efficiency.

在上面的代码中,通过使用 lock 块,启动该方法的第一个线程将接管 locker 资源并进入 lock 块。由于 locker 资源不可用,因此进入该方法的其余线程将停止,以便前一个线程释放资源。释放资源后,下一个线程立即收到资源并进入 lock 块。线程不安全的问题得到了解决,但代码仍然可以改进。这种实现方法的重要问题是效率。

For example, thread number 1 enters the GetInstance method and receives the locker. Thread number 2 enters and stops behind the lock block. Thread number 1 creates the object and exits the block, and thread number 2 enters the lock block. Meanwhile, thread number 3 enters the GetInstance method, and since the locker is owned by thread number 2, it must wait. But the waiting is useless, and thread number 3 can directly receive the object and return it. With this explanation, the preceding code can be optimized as follows:

例如,线程 1 输入 GetInstance 方法并接收保险箱。线程 2 进入并停止在锁块后面。线程 1 创建对象并退出块,线程 2 进入 lock 块。同时,线程 3 进入 GetInstance 方法,并且由于锁由线程 2 拥有,因此它必须等待。但是等待没用,线程 3 可以直接接收对象并返回。通过此说明,可以按如下方式优化上述代码:

public static DbConnectionManager GetInstance()
{
    if (_instance != null)
    return _instance;
    lock (locker)
    {
        if (_instance == null)
        _instance = new();
        return _instance;
    }
}

This method is called double-check locking and can improve overall performance.
此方法称为 double-check locking,可以提高整体性能。

The second method to implement thread-safe is to mark the GetInstance method as Synchronized. Using this method, the GetInstance method is implemented as follows:
实现线程安全的第二种方法是将 GetInstance 方法标记为 Synchronized。使用此方法,GetInstance 方法的实现方式如下:

[MethodImpl(MethodImplOptions.Synchronized)]
public static DbConnectionManager GetInstance()
{
    if (_instance == null)
    _instance = new();
    return _instance;
}

This method has an important difference compared to the lock method; that is, only one thread is allowed to enter the method. The object is protected by using a lock, and the whole method is protected by using a synchronized method.

与 lock 方法相比,此方法具有重要区别;也就是说,只允许一个线程进入该方法。使用锁保护对象,并使用同步方法保护整个方法。

Apart from the preceding methods, there are other methods, such as using Lazy or Monitor class to implement a thread-safe singleton.
除了上述方法外,还有其他方法,例如使用 Lazy 或 Monitor 类来实现线程安全的单例。

Participants:
参与者:

  • Singleton: In the preceding scenario, it is the DbConnectionManager that is responsible for defining the object creation process. This class should always try to create only one object of the target class.
    Singleton:在前面的场景中,DbConnectionManager 负责定义对象创建过程。此类应始终尝试仅创建目标类的一个对象。

Notes:

  • By using this design pattern, in the object creation process, any changes which are needed can be applied by applying changes in the GetInstance method. For example, by having this structure, it is possible to provide conditions so that there are always two objects from a class. To implement this logic, it is enough to change the GetInstance method.
    通过使用这种设计模式,在对象创建过程中,可以通过在 GetInstance 方法中应用更改来应用所需的任何更改。例如,通过具有此结构,可以提供条件,以便一个类中始终有两个对象。要实现此逻辑,只需更改 GetInstance 方法即可。

  • In using this design pattern, you should pay attention to its difference from static classes. Among the most important differences between the static class and the use of the singleton design pattern, we can mention the impossibility of using static classes in inheritance, which makes static classes not extensible.
    在使用此设计模式时,应注意它与 static 类的区别。在 static 类和使用 singleton 设计模式之间最重要的区别中,我们可以提到在继承中使用 static 类是不可能的,这使得 static 类不可扩展。

  • Abstract factory, factory method, and builder design patterns can be implemented using Singleton.
    抽象工厂、工厂方法和构建器设计模式可以使用 Singleton 实现。

  • The initialization process can be implemented in both Lazy and Early. The difference between these two methods is when the desired object is initialized. In the Lazy method, the object is initialized when it is needed. In the Early method, the object is initialized when the class is loaded. The lazy method is used in the preceding scenario. To implement the early method, you can do the following:
    初始化过程可以在 Lazy 和 Early 中实现。这两种方法之间的区别在于初始化所需对象的时间。在 Lazy 方法中,对象在需要时初始化。在 Early 方法中,对象在加载类时初始化。在前面的场景中使用了 lazy 方法。要实现早期方法,您可以执行以下作:

    public class DbConnectionManager
    {
    private static DbConnectionManager _instance = new();
    private DbConnectionManager() { }
    public static DbConnectionManager GetInstance() => _instance;
    }

Consequences: Advantages
后果:优势

  • It can be ensured that there is one object in the class.
    可以确保类中有一个对象。

  • It is possible to access the object. Since a method is responsible for providing the object, it is possible to have better control over who requested the object and when.
    可以访问该对象。由于方法负责提供对象,因此可以更好地控制请求对象的人员和时间。

Consequences: Disadvantages
后果:缺点

  • The implementation of the thread-safe structure can be complicated, and this task can affect performance due to the use of locking mechanisms.
    线程安全结构的实现可能很复杂,并且由于使用锁定机制,此任务可能会影响性能。

  • Writing unit tests can be complicated.
    编写单元测试可能很复杂。

Applicability:
适用性:

  • When we need only one object of the class to be available to the user.
    当我们只需要 class 的一个对象可供用户使用时。
  • When we need to have more control over the object and its data content, we can use this design pattern instead of using Global Variables.
    当我们需要对对象及其数据内容进行更多控制时,我们可以使用这种设计模式而不是使用 Global Variables。

Related patterns:
相关模式:
Some of the following design patterns are not related to the Singleton design pattern, but to implement this design pattern, checking the following design patterns will be useful:
以下一些设计模式与 Singleton 设计模式无关,但要实现此设计模式,检查以下设计模式将很有用:

  • Facade
  • Abstract Factory
  • Factory Method
  • Builder
  • Prototype
  • Flyweight
  • Observer
  • State

Conclusion

总结

In this chapter, you got acquainted with various creational design patterns and learned how to manage the initialization of objects according to different conditions.
在本章中,您熟悉了各种创建设计模式,并学习了如何根据不同条件管理对象的初始化。

In the next chapter, you will learn about structural design patterns and learn how to manage relationships between classes and objects.
在下一章中,您将了解结构设计模式,并学习如何管理类和对象之间的关系。