“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。 ——Christopher Alexander
设计模式(Design Pattern)是对软件开发过程中反复出现的设计问题所提供的通用解决方案。它不是代码,而是经过验证的“最佳实践”,以一种结构化的方式记录了解决问题的思想。
GOF(Gang of Four,四人帮)指的是设计模式的奠基者——Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。这四位作者在 1994 年出版了经典著作 《Design Patterns: Elements of Reusable Object-Oriented Software》,首次系统化地总结了 23 种面向对象设计模式。
对于C++程序猿来说,需要将底层思维与抽象思维都进行分析。
**底层思维:**向下,如何把握机器底层从微观理解对象构造
• 语言构造 • 编译转换 • 内存模型 • 运行时机制
**抽象思维:**向上,如何将我们的周围世界抽象为程序代码
• 面向对象 • 组件封装 • 设计模式 • 架构模式
良好的设计模式可以根据语言在底层的逻辑和在语言逻辑层面的设计进行相结合,以此来提高代码质量和开发效率。
所以,在讨论设计模式之前,必须再次理解面向对象的核心思想:
面向对象通过以上三大特性为设计模式奠定了基础,是理解设计模式的关键。
建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要败。然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。 ——Object-Oriented Analysis and Design with Applications
复杂性是软件设计中的一大挑战。软件的复杂性源于多个维度:
原因在后期会进行功能的添加或者修改,但是在刚开始程序进行设计的时候的代码逻辑无法使得在基础上直接添加新功能,所以只能在原来的上面进行强行添加,也就是
if - else
屎山设计的由来~
人们面对复杂性有一个常见的做法:即分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。
更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节,而去处理泛化和理想化了的对象模型。
我们以一个现实问题为例:计算矩形的面积和周长。
在结构化设计中,数据和功能是分离的。函数主要以操作为中心,而数据仅作为参数传递。
#include <iostream>
using namespace std;
// 数据结构:存储矩形的长度和宽度
struct Rectangle {
double length;
double width;
};
// 函数:计算面积
double calculateArea(const Rectangle& rect) {
return rect.length * rect.width;
}
// 函数:计算周长
double calculatePerimeter(const Rectangle& rect) {
return 2 * (rect.length + rect.width);
}
int main() {
// 创建一个矩形
Rectangle rect = {5.0, 3.0};
// 调用函数进行操作
cout << "Area: " << calculateArea(rect) << endl;
cout << "Perimeter: " << calculatePerimeter(rect) << endl;
return 0;
}
特点:
Rectangle
) 和操作(calculateArea
和 calculatePerimeter
)是独立的。在面向对象设计中,数据和功能封装在一个类中,操作直接基于对象调用。
#include <iostream>
#include <cmath> // 为了计算对角线
using namespace std;
// 面向对象设计:定义矩形类
class Rectangle {
private:
double length;
double width;
public:
// 构造函数
Rectangle(double l, double w) : length(l), width(w) {}
// 方法:计算面积
double calculateArea() const {
return length * width;
}
// 方法:计算周长
double calculatePerimeter() const {
return 2 * (length + width);
}
// 方法:计算对角线长度
double calculateDiagonal() const {
return sqrt(length * length + width * width);
}
// 设置长度和宽度
void setDimensions(double l, double w) {
length = l;
width = w;
}
// 显示矩形信息
void display() const {
cout << "Rectangle [Length: " << length << ", Width: " << width << "]" << endl;
}
};
int main() {
// 创建一个矩形对象
Rectangle rect(5.0, 3.0);
// 调用对象方法进行操作
rect.display();
cout << "Area: " << rect.calculateArea() << endl;
cout << "Perimeter: " << rect.calculatePerimeter() << endl;
cout << "Diagonal: " << rect.calculateDiagonal() << endl;
return 0;
}
特点:
length
, width
) 和操作(如 calculateArea
, calculatePerimeter
)被封装在类中。特点 | 结构化设计 | 面向对象设计 |
---|---|---|
数据与操作的关系 | 数据和操作分离,函数作用于独立的数据 | 数据和操作封装在一个对象内,操作与数据密切相关 |
扩展性 | 新增功能需要新增函数,整体设计不易扩展 | 新增功能只需添加类方法,设计更具扩展性 |
维护性 | 操作逻辑分散在多个函数中,难以维护 | 操作封装在类中,逻辑清晰,易于维护 |
代码复用性 | 数据结构和操作复用性较差 | 类和方法具有较高的复用性 |
示例中的实现复杂度 | 数据操作较为简单,但难以应对复杂系统 | 初期实现略复杂,但适合复杂系统 |
通过以上对比,可以直观地看出,结构化设计更适合小型、单一功能的程序,而面向对象设计在解决复杂系统时更具优势。
结构化设计 | 面向对象设计 |
---|---|
以功能为中心,数据为次要目标 | 以对象为中心,功能服务于对象 |
数据和操作分离 | 数据和操作封装在对象内部 |
难以复用和扩展 | 具有较好的复用性和扩展性 |
面向对象设计通过关注对象及其交互,解决了结构化设计中模块间强耦合的问题,为设计模式的实施提供了基础。
复用是软件设计的核心目标之一。设计模式通过以下方式实现复用:
设计模式是应对软件复杂性的重要工具,是面向对象思想的升华。通过学习和实践设计模式,开发者可以设计出灵活、可扩展且易维护的系统。对于初学者来说,了解设计模式的基本分类和使用场景,是深入学习的第一步。
关于具体不同设计模式是如何设计的,请阅读笔者该专栏其他文章。