创建型模式

创建型模式提供了创建对象的机制

单例模式

单例模式解决了两个问题:

  • 保证一个类只有一个实例

  • 为此实例提供一个全局访问节点

单例模式用于创建一个系统中唯一的对象,例如打印机、全局内存池。单例模式确保当你通过任意方法获取唯一对象时,得到的对象都是一样的。

单例模式实现的步骤在所有语言中都是相似的:

  1. 将构造函数设为私有,防止类的对象被私自创建

  2. 添加静态方法 getInstance 来返回此单一对象

单例模式的优缺点:

优点:

  • 你可以保证一个类只有一个实例

  • 获得了一个指向该实例的全局访问节点

  • 单例对象只会被初始化一次

缺点:

  • 违反了单一职责原则。此模式同时解决了两个问题

  • 可能掩盖不良设计。例如程序各组件之间相互了解过多等

  • 多线程环境下比较麻烦

  • 单元测试比较麻烦

单例模式的 C++ 实现

C 中实现单例模式是最简单的,因为 C 11 保证静态对象只会被构造一次

class Singleleton {
   Singleleton() = default;

public:
   static Singleleton *getInstance() {
      static Singleleton instance;
      return &instance;
   }
};

C++ 创建单例模式的时候一定要格外注意:如果函数不是类静态成员函数,一定不要添加 static,也就是说不要出现这样的签名:

static A& GetInstance(){
    static A a;
    return a;
}

这种情况下,每个 cpp 文件都会有一个单独的此函数实现,因而不同文件中得到的实例的地址时不同的。

简单工厂模式

简单工厂模式将创建对象的任务移交给工厂,通过产品接口对产品进行约束。

@startuml class PhoneFactory abstract class Phone

Phone<..ApplePhone

PhoneFactory <..Phone @enduml

我们通过 Phone 对产品接口进行约束,然后通过 PhoneFactory 去获取对象。通过这种方式,以后我们再拓展产品时只需要 继承接口类在工厂类里添加条件分支 。而客户也只需要知道产品的名称就行,产品对应的类名可以根据需求任意更改。

简单工厂模式做到了:

  • 可复用:新增的产品可以直接使用已有接口。要使用产品,只需要导入工厂类即可

  • 松耦合:新增的产品不会对已有产品的代码造成任何影响。

简单工厂模式解决了在实现上面两个优点的情况下 工厂类实例化哪一个产品 的工作。

class Phone {
public:
   virtual ~Phone() = 0;
};

Phone::~Phone() {

}

class MiPhone : public Phone {
public:
   void *operator new(size_t size) {
      cout << "MiPhone" << endl;
      return malloc(size);
   }
};

class ApplePhone : public Phone {
public:
   void *operator new(size_t size) {
      cout << "Apple Phone" << endl;
      return malloc(size);
   }
};

class PhoneFactory {
public:
   Phone *createPhone(string const &name) {
      if (name == "Mi")
            return new MiPhone;
      else if (name == "Apple")
            return new ApplePhone;
      else return nullptr;
   }
};

// 使用
auto factory = new PhoneFactory;
auto mi = factory->createPhone("Mi");
auto apple = factory->createPhone("Apple");

使用场景:

  • 创建数据库连接时选择不同的数据库驱动。(例如:https://doc.qt.io/qt-5/qsqldatabase.html#addDatabase[QSqlDatabase])。

  • 连接邮件服务器连接客户端:可选择 POP, IMAPHTTP 三种不同的协议。

  • 获取日志储存器:可选择 本地硬盘系统事件, 远程服务器 等。

工厂模式

工厂模式使用工厂方法代替了对构造函数的直接使用。工厂方法返回的对象被称为产品,而系统中的方法都是直接对产品接口操作。这种方法易于拓展产品

工厂模式的实现步骤为:

  1. 声明产品接口

  2. 实现产品接口

  3. 实现工厂方法

  4. 操作产品

与产品类似,工厂受工厂接口的约束。所有的具体工厂都需要实现工厂接口。

@startuml

interface Dialog interface Button

interface Dialog\{

Button* createButton()

}

class WindowsDialog\{
  • Button* createButton()

}

class WebDialog\{
  • Button* createButton()

}

Dialog <.. WebDialog

WindowsDialog <.. WindowsButton WebDialog <..HTMLButton

Button <..HTMLButton

@enduml

工厂方法不一定每次都创建新的方法,还可以返回已缓存的对象。

工厂方法的优点:

  • 避免创建者和具体产品之间的紧密耦合

  • 单一职责:你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护

  • 开闭原则:无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。

缺点:

  • 工厂模式会引入许多新的子类,可能会导致代码更复杂

工厂模式的 C++ 实现

class Dialog{
public:
   virtual Phone* createButton() = 0;
};

class WindowsDialog : public Dialog{
public:
   Phone * createButton() override{
      return new WindowsButton;
   }
};

class WebDialog: public Dialog{
public:
   Phone * createButton() override{
      return new HTMLButton;
   }
};
// 使用

WindowsDialog* windowsDialog = new WindowsDialog;
Button* button = windowsDialog->createButton();

WindowsDialog* webDialog = new WebDialog;
Button* button = webDialog->makePhone();

抽象工厂模式

抽象工厂是对工厂模式的进一步抽象。工厂模式是单一产品的工厂,而抽象工厂是系列产品的工厂。

例如现在有家具工厂,则现代工厂只创建现代风格的桌子、现代风格的凳子、现代风格的台灯。而古典工厂只创建古典风格的桌子、古典风格的凳子、古典风格的台灯。

所有的产品依然遵循对应的接口

抽象工厂模式的步骤:

  1. 创建各种产品和工厂的接口

  2. 实现产品接口和工厂接口

  3. 使用具体工厂来生产具体产品

@startuml

interface Chair interface Table

interface Factory\{

Chair* createChair() Table* createTable()

}

Factory <.. ClassicFactory

Chair <.. ClassicChair

Table <.. ClassicTable

ModernFactory <.. ModernChair ModernFactory <.. ModernTable

ClassicFactory <.. ClassicChair ClassicFactory <.. ClassicTable

@enduml

抽象工厂模式的优点:

  • 你可以确保同一工厂生成的产品相互匹配

  • 你可以避免客户端和具体产品代码的耦合

  • 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护

  • 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码

缺点:

  • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂

建造者模式

如果一个类的构成十分复杂,与其提供一个参数非常多的构造函数,不如类的构造分解到多个步骤中,然后使用工具来生成对象。

建造者模式的步骤为:

  1. 声明 Builder 接口

  2. 实现 Builder 接口

  3. 创建 Director 来借用 Builder 生成对象

例如在制造汽车时,汽车的外壳、引擎、商标、材料都会有变化,与其为每个汽车创建一个类,不如使用建造者模式:

@startuml

interface Car

interface Builder \{

reset() setSeats(number) setEngine(engine) setTripComputer() setGPS()

}

class Director \{
  • Car* makeSUV(builder)

  • Car* makeSportsCar(builder)

}

Builder <.. CarManualBuilder

Director <.. CarBuilder Director <.. CarManualBuilder

@enduml

在使用时,首先创建一个 Builder 对象,然后对其设置参数,最后将其传入 Director 完成构建

原型模式

原型模式主要解决未知对象的拷贝问题。在使用基类指针操纵子类对象时,指针的确切类型并不知道,而构造函数又没有多态,这就导致没法拷贝对象。通过让所有子类都实现 Clone() 接口,就解决了对象的拷贝问题。

原型模式的 C++ 实现

class Cloneable{
public:
   virtual Cloneable* clone() = 0;
};

class Paper: public Cloneable{
   char* content = nullptr;
public:
   explicit Paper(const char* str){
      size_t len = strlen(str) + 1;
      content = (char*)malloc(len);
      strcpy_s(content, len, str);
   }
   Paper(const Paper& b){
      // 当持有 Paper 对象时,直接使用拷贝构造函数
      if(content) free(content);
      size_t len = strlen(b.content) + 1;
      content = (char*)malloc(len);
      strcpy_s(content, len, b.content);
   }
   Cloneable * clone() override{
      // 当持有 Cloneable 对象时,使用 clone() 进行拷贝
      Paper* duplicate = new Paper(*this);
      return duplicate;
   }
};
// 使用
Cloneable* paper = new Paper("Hello, world!");
Cloneable* paper1 = paper->clone();
cout<<boolalpha<<(paper == paper1);
Last moify: 2023-06-04 06:20:54
Build time:2025-07-18 09:41:42
Powered By asphinx