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.
在下一章中,您将了解结构设计模式,并学习如何管理类和对象之间的关系。

Leave a Reply

Your email address will not be published. Required fields are marked *