c++设计模式第一回(简介)
2026/7/28 17:55:24 网站建设 项目流程

由于之前对c++的了解仅限于代码层面,解决问题的思路往往比较微观,因此想深入学习下设计模式,从宏观上能够了解别人设计代码时的思想,为自己看待问题多提供思路,能够编出执行效率更高的代码。如果又碰巧也在专研设计模式的同学也欢迎在我的文章下面一起评论,共同进步~~
首先我们来简单的了解一下设计模式:
“每一个设计模式描述了一个在我们周围不断重复发生的问题,以及该问题解决方案的核心。这样我们就能一次又一次使用该方案解决问题。”这是最早对于设计模式的定义,其实这是一个建筑学家的定义,但是我们说写代码就好比造房子,你编出来的代码看起来像是豪华别墅还是平民社区,这就需要看你是否有深厚的功底。
先来谈一下面向对象,我们说我们的代码编出来时属于一种抽象思维的实现,也就是把你的想法变为现实,而电脑编译并且执行你写的代码就是属于一种底层思维,电脑需要为上层的代码运行提供良好的机器环境。众所周知面向对象有三大属性,(大家跟我一起读)多态、继承、封装。多态指的是改用对象的实现,封装是指隐藏内部的实现,继承是指复用现有的代码。

面向对象

然后我们说设计模式的一个主要的特点是抽象,可以将需要看似不是一个类型的问题抽象到一个问题,这样就可以使用某种模式解决这个问题。然后当我们最先开始学习C语言或者其他面向过程的编程语言时,我们解决一个很复杂的问题第一个想到的是去将其分解,将一个大问题分解成许多个小问题,这样只需要解决每个小问题就可以把整个大问题解决掉。下面我们就来通过code深入的理解一下分解和抽象的区别。

下面的代码是这样的,我在编写这个程序的时候使用的是伪代码,并且有些代码为了书写方便没有太遵循C++的编译规则,如果其他同学需要具体实现的话可以遵循正常的c++编译规则进行实现。因为如果要把整个可以编译执行的代码贴过来的话会很复杂,我们只需要了解里面的思想就可以。好,这个例子实现的是一个简单的画图工具,一个画图工具必不可少的有描画的点、线、形状等。

分解代码如下:

// 头文件定义 #ifndef SHAPE_H #define SHAPE_H // 描画的点的类 class Point { private: int x; int y; }; // 描画线条的类,需要知道起点和终点 class Line { public: Line(const Point& start, const Point& end) { this->start = start; this->end = end; } public: Point start; Point end; }; // 描画矩形的类,需要知道和描画的矩形的长和宽 class Rect { public: Rect(const Point& leftUp, int width, int height) { this->lefUp = leftUp; this->width = width; this->height = height; } private: Point lefUp; int width; int height; }; // 如果需要增加新的形状的话只需要添加新的类 #endif // SHAPE_H // 下面是具体实现 #include "Shape.h" #include <vector> // 这个类是用于在画框上进行描画的类 class MainForm :public Form { public: MainForm() { // ... } private: Point p1; Point p2; // 用于存放Line和Rect的类 vector<Line> lineVector; vector<Rect> rectVector; protected: // 操作的回调函数 void onMouseDown(const MouseEventArg& e); void onMouseUp(const MouseEventArg& e); void onPaint(const MouseEventArg& e); }; void MainForm::onMouseDown(const MouseEventArg &e) { p1.x = e.x; p1.y = e.y; Form::onMouseDown(e); } void MainForm::onMouseUp(const MouseEventArg &e) { p2.x = e.x; p2.y = e.y; // 判断当前画的是线条还是矩形,如果新加了其他的形状也需要新家判断去满足此形状的描画 if (moduleLine.checked) { Line line(p1, p2); lineVector.push_back(line); } else if (moduleRect.checked) { int width = trans(p2.x - p1.x); int height = trans(p2.y - p1.y); Rect rect(p1, width, height); rectVector.push_back(rect); } // ... // 设置好之后需要刷新下界面进行显示 this->refresh(); // ... From::onMouseUp(e); } void MainForm::onPaint(const MouseEventArg &e) { // 真正描画形状时的回调,如果有新的形状添加进来就需要增加新的循环进行描画 for (int i = 0; i < lineVector.size(); i++) { e.Grapic.Draw(Pens.RED, lineVector[i].start.x, lineVector[i].start.y, lineVector[i].end.x, lineVector[i].end.y); } for (int i = 0; i < rectVector.size(); i++) { e.Grapic.Draw(Pens.RED, rectVector[i].leftUp, rectVector[i].width, rectVector[i].height); } }

从上面的例子可以看出,如果要新加形状的话,需要变更的地方有很多,首先要新加类,然后要在回调的时候加上对应的形状的判断,这样如果形状有成百上千个的话,变更量会很巨大,因此这种方法虽然易懂但是变更量太大。

我们再来看下抽象的你思维方法会怎么应对增加形状时的变更:

// 头文件定义 #ifndef SHAPE_H #define SHAPE_H // 基类shape class Shape { public: // 设置Draw的纯虚函数,以及虚方法 virtual void Draw(const Graphic& g) = 0; virtual ~Shape(){} }; // 坐标点的类,继承自Shape class Point { private: int x; int y; }; // 描画线条的类,继承自Shape class Line :public Shape { public: Line(const Point& start, const Point& end) { this->start = start; this->end = end; } // 添加虚函数Draw,描画自己的图形 virtual Draw(const Graphic& g) { g.DrawLine(Pens.RED, start.x, start.y, end.x, end.y); } private: Point start; Point end; }; // 描画矩形的类 class Rect :public Shape { public: Rect(const Point& leftUp, int width, int height) { this->lefUp = leftUp; this->width = width; this->height = height; } // 添加虚函数Draw,描画自己的图形 virtual Draw(const Graphic& g) { g.DrawLine(Pens.RED, lefUp, width, height); } private: Point lefUp; int width; int height; }; #endif // SHAPE_H // cpp的具体实现 #include "Shape.h" #include <vector> class MainForm :public Form { public: MainForm() { // ... } private: Point p1; Point p2; // 使用基类的指针,这样可以实现复用 vector<Shape*> shapeVector; protected: void onMouseDown(const MouseEventArg& e); void onMouseUp(const MouseEventArg& e); void onPaint(const MouseEventArg& e); }; void MainForm::onMouseDown(const MouseEventArg &e) { p1.x = e.x; p1.y = e.y; Form::onMouseDown(e); } void MainForm::onMouseUp(const MouseEventArg &e) { p2.x = e.x; p2.y = e.y; // 判断形状时依旧需要增加类型变化,但是在后面加上工厂模式之后就可以不用变化 if (moduleLine.checked) { shapeVector.push_back(new line(p1, p2)); } else if (moduleRect.checked) { int width = trans(p2.x - p1.x); int height = trans(p2.y - p1.y); shapeVector.push_back(new rect(p1, width, height)); } // ... this->refresh(); // ... From::onMouseUp(e); } void MainForm::onPaint(const MouseEventArg &e) { // 从shapeVector中取出的元素类型为Shape*,这个指针可以根据具体push_back进去的形状 // 调用不同的函数 for (int i = 0; i < shapeVector.size(); i++) { shapeVector[i]->Draw(e.Graphics); } }

我们使用抽象的思维时将所有的形状都继承一个基类,然后每个子类实现自己的Draw方法,这样就可以在调用时依赖多态性调用到具体的Draw方法,即使有很多形状,依然只需要一个for循环取出形状。

通过上面的例子可以看出:使用抽象的思维解决问题时,可以更好的应对变化,复用性更强,分解的思维虽然可以解决问题,但是一旦客户的需求变化,那么变更就会增多很多。 所以两种思维的应用场景不一致。

下一回会正式进入设计模式的详解。

本次反向内容据来自于自己通过看c++设计模式的视频的一些总结,设计模式原视频可以参考:极客李建忠老师讲的23种设计模式,我认为讲的很到位。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询