创建型模式
创建型模式提供了创建对象的机制
单例模式
单例模式解决了两个问题:
保证一个类只有一个实例
为此实例提供一个全局访问节点
单例模式用于创建一个系统中唯一的对象,例如打印机、全局内存池。单例模式确保当你通过任意方法获取唯一对象时,得到的对象都是一样的。
单例模式实现的步骤在所有语言中都是相似的:
将构造函数设为私有,防止类的对象被私自创建
添加静态方法 getInstance 来返回此单一对象
单例模式的优缺点:
优点:
你可以保证一个类只有一个实例
获得了一个指向该实例的全局访问节点
单例对象只会被初始化一次
缺点:
违反了单一职责原则。此模式同时解决了两个问题
可能掩盖不良设计。例如程序各组件之间相互了解过多等
多线程环境下比较麻烦
单元测试比较麻烦
单例模式的 C++ 实现
C 中实现单例模式是最简单的,因为 C 11 保证静态对象只会被构造一次
class Singleleton {
Singleleton() = default;
public:
static Singleleton *getInstance() {
static Singleleton instance;
return &instance;
}
};
C++ 创建单例模式的时候一定要格外注意:如果函数不是类静态成员函数,一定不要添加 static,也就是说不要出现这样的签名:
这种情况下,每个 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, IMAP, HTTP 三种不同的协议。
获取日志储存器:可选择 本地硬盘, 系统事件, 远程服务器 等。
工厂模式
工厂模式使用工厂方法代替了对构造函数的直接使用。工厂方法返回的对象被称为产品,而系统中的方法都是直接对产品接口操作。这种方法易于拓展产品
工厂模式的实现步骤为:
声明产品接口
实现产品接口
实现工厂方法
操作产品
与产品类似,工厂受工厂接口的约束。所有的具体工厂都需要实现工厂接口。 |
@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();
抽象工厂模式
抽象工厂是对工厂模式的进一步抽象。工厂模式是单一产品的工厂,而抽象工厂是系列产品的工厂。
例如现在有家具工厂,则现代工厂只创建现代风格的桌子、现代风格的凳子、现代风格的台灯。而古典工厂只创建古典风格的桌子、古典风格的凳子、古典风格的台灯。
所有的产品依然遵循对应的接口
抽象工厂模式的步骤:
创建各种产品和工厂的接口
实现产品接口和工厂接口
使用具体工厂来生产具体产品
@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
抽象工厂模式的优点:
你可以确保同一工厂生成的产品相互匹配
你可以避免客户端和具体产品代码的耦合
单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护
开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码
缺点:
由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂
建造者模式
如果一个类的构成十分复杂,与其提供一个参数非常多的构造函数,不如类的构造分解到多个步骤中,然后使用工具来生成对象。
建造者模式的步骤为:
声明 Builder 接口
实现 Builder 接口
创建 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);