23种设计模式之C++实践
- 1. 简介
- 2. 基础知识
- 3. 设计模式
-
- (一)创建型模式
-
- 1. 单例模式
-
- 1.2 饿汉式单例模式
- 1.3 懒汉式单例模式
- 比较
- IoDH
- 单例模式总结
- 2. 简单工厂模式
-
- 简单工厂模式总结
- 3. 工厂方法模式
-
- 工厂方法模式总结
- 4. 抽象工厂模式
-
- 抽象工厂模式总结
- 5. 原型模式
-
- 原型模式总结
- 6. 建造者模式
-
- 建造者模式总结
1. 简介
设计模式是一门技术,更是一门艺术,它为构建可维护性和可复用性俱佳的软件而诞生。
2. 基础知识
- 设计模式(Design Pattern):是一套被反复使用的,多数人知晓的,经过分类编目的代码设计经验的总结,使用设计模式是为了可以重用代码,让代码更容易被理解并提高代码的可靠性。
- UML(United Modeling Language):
- 类
- 关联关系(实线):通常将一个类的对象作为另一个类的成员变量。
- 双向关联
- 单向关联
- 自关联
- 多重性关联
- 聚合关系(空心菱形直线):成员是整体的一部分,但是成员可以脱离整体存在。通常通过
set()
函数初始化成员变量。 - 组合关系(实心菱形直线):整体控制成员的声明周期,整体不存在,则成员不存在。通常通过构造函数初始化成员变量。
- 依赖关系(带箭头虚线):类的改变影响到使用该类的其他类,则其他类依赖该类。通常通过将一个类的对象作为另一个类中方法的参数体现。
- 泛化关系/继承关系(空心三角形实线):父类与子类。
- 接口与实现关系(空心三角形虚线):在接口类中声明抽象函数,在实现类中实现函数。
- 关联关系(实线):通常将一个类的对象作为另一个类的成员变量。
- 类
- 面向对象设计原则:
- 单一职责原则(Single Responsibility):一个类只负责一个功能领域中的相应职责。或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。高内聚,低耦合。
- 开闭原则(Open-Closed Principle,OCP):一个软件实体应该对外扩展开放,对修改关闭。即软件实体尽量在不修改原有代码的基础下进行扩展。抽象化。
- 里氏代换原则(Liskov Substitution Principle,LSP):所有引用父类的地方必须能透明地使用其子类的对象。父类抽象化
- 依赖倒转原则(Dependency Inversion Principle,DIP):抽象不应该依赖于细节,细节应该依赖于抽象。即针对接口编程,而不是针对实现编程。参数抽象化。
- 接口隔离原则(Interface Segregation Principle,ISP):使用多个专用的接口,而不适用单一的总接口,即客户端不应该依赖那些它不需要的接口。
- 合成复用原则(Composite Reuse Principle,CRP):尽量使用对象组合,而不是继承来达到复用的目的。
- 迪米特法则/最少知识原则(Law of Demeter/Least Knowledge Principle,LoD/LKP):一个软件实体应该尽可能少的与其他实体发生相互作用。
3. 设计模式
(一)创建型模式
1. 单例模式
-
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类成为单例类,它提供全局访问的方法。
-
要点:
-
某个类只能有一个实例
-
它必须自行创建这个实例
-
它必须自行向整个系统提供这个实例
-
-
结构图:
-
使用场景实例
Windows任务管理器
在一个Windows系统中,任务管理器存在唯一性。
-
代码实例
// TaskManager.h class TaskManager { private: /** * @brief 初始化窗口 * */ TaskManager() { } public: /** * @brief 显示进程 * */ void displayProcesses(); /** * @brief 显示服务 * */ void displayServices(); private: static TaskManager* tm; public: static TaskManager* getInstance(); }; // TaskManager.cpp TaskManager* TaskManager::tm = nullptr; void TaskManager::displayProcesses() { printf("显示进程……\n"); return; } void TaskManager::displayServices() { printf("显示服务……\n"); return; } TaskManager* TaskManager::getInstance() { if (tm == nullptr) { tm = new TaskManager(); } return tm; }
-
代码测试
-
测试代码:
int main(int argc, char** argv) { printf("I'm Singleton Pattern!\n"); // begin test SingleTonNS::TaskManager* tm = SingleTonNS::TaskManager::getInstance(); tm->displayProcesses(); tm->displayServices(); // end test return 0; }
-
输出
I’m Singleton Pattern!
显示进程……
显示服务……
-
1.2 饿汉式单例模式
-
饿汉式单例模式(Eager Singleton):在定义静态变量的时候实例化单例类,因此在类加载时就已经创建了单例对象
-
结构图
-
代码示例
// TaskManager.h class TaskManager { private: /** * @brief 初始化窗口 * */ TaskManager() { } public: /** * @brief 显示进程 * */ void displayProcesses(); /** * @brief 显示服务 * */ void displayServices(); private: static TaskManager* tm; public: static TaskManager* getInstance(); }; // TaskManager.cpp // 饿汉式单例模式初始化 TaskManager* TaskManager::tm = new TaskManager(); void TaskManager::displayProcesses() { printf("显示进程……\n"); return; } void TaskManager::displayServices() { printf("显示服务……\n"); return; } TaskManager* TaskManager::getInstance() { return tm; }
1.3 懒汉式单例模式
- 见单例模式实现方式。
比较
-
饿汉式单例类
-
优点
-
无需考虑多线程访问问题,可以确保实例的唯一性
-
调用速度与反应时间更快
-
-
缺点
-
资源利用效率更低
-
加载时间更长
-
-
-
懒汉式单例类
- 优点
- 延迟加载,无需一直占用系统资源
- 缺点
- 必须处理好多线程同时访问的问题,可能引起性能受影响
- 优点
IoDH
- 有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是肯定的,即Initialization on Demand Holder技术
- IoDH:在单例类中增加一个静态内部类,在该内部类中,创建单例对象,再将该单例对象通过
getInstance()
方法返回给外部使用。 - 使用IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能,但是与编程语言本身的特性相关,很多面向对象语言不支持IoDH,例如C++。支持的语言有Java。
单例模式总结
- 优点
- 提供了对唯一实例的受控访问。
- 节约系统资源
- 允许可变数目的实例。
- 缺点
- 没有抽象层,扩展较为困难
- 职责过重,一定程度上违反了单一职责原则。
- 很多面向对象语言(例如Java,C#)的运行环境都提供了自动垃圾回收技术,因此,如果实例化的共享对象长期不被利用,会自动销毁并回收资源,下次利用时重新实例化,这将导致共享的单例对象状态的丢失。
- 适用场景
- 系统只需要一个实例对象
- 客户调用类的单个实例只允许使用一个公共访问点。
2. 简单工厂模式
-
简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建类的实例通常具有共同的父类。
-
要点:
- 当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
-
结构图:
-
适用场景实例
图标库:为应用系统提供各种不同外观的图表,例如柱状图、饼状图、折线图等等
-
代码示例
// Chart.h
/**
* @brief 图表类接口
*
*/
class Chart {
public:
virtual void display() = 0;
};
/**
* @brief 柱状图
*
*/
class HistogramChart : public Chart {
public:
HistogramChart();
void display() override;
};
/**
* @brief 饼状图
*
*/
class PieChart : public Chart {
public:
PieChart();
void display() override;
};
/**
* @brief 折线图
*
*/
class LineChart : public Chart {
public:
LineChart();
void display() override;
};
// ChartFactory.h
class ChartFactory {
public:
static Chart* getChart(std::string type);
};
// Chart.cpp
HistogramChart::HistogramChart() {
printf("创建柱状图!\n"); }
void HistogramChart::display() {
printf("展示柱状图!\n");
return;
}
PieChart::PieChart() {
printf("创建饼状图!\n"); }
void PieChart::display(