网站做系统叫什么软件,企业网站轮播图怎么做,哔哩哔哩免费观看入口,百度公司图片#x1f600;大家好#xff0c;我是白晨#xff0c;一个不是很能熬夜#x1f62b;#xff0c;但是也想日更的人✈。如果喜欢这篇文章#xff0c;点个赞#x1f44d;#xff0c;关注一下#x1f440;白晨吧#xff01;你的支持就是我最大的动力#xff01;#x1f4…大家好我是白晨一个不是很能熬夜但是也想日更的人✈。如果喜欢这篇文章点个赞关注一下白晨吧你的支持就是我最大的动力 文章目录 前言设计模式概述一、设计模式的历史二、理解面向对象三、软件设计的复杂性四、软件设计的目标 后记 前言 大家好我是白晨。不知不觉已经了很久了白晨在这里给大家道个歉。
本次为大家带来的是全新的专栏——设计模式许多同学学习了面向对象的概念但是面向对象在实际开发中到底好不好用、到底如何使用这个问题可能很多人都无法回答。同时在现在的面试中设计模式被提及的概率越来越高“你都使用过什么设计模式”、“在xxx中都使用了哪些设计模式“这些问题相信有过面试经历的人都会被问到但是即使是一个编程的老鸟做到准确无误的使用设计模式和理解设计模式也是非常困难的。
基于以上两点白晨想详细介绍每种设计模式的设计思想重点、编程实现以及如何使用等让大家可以准确理解设计模式的思想力求做到通俗易懂。
本篇为引导篇在本篇中主要探讨一个问题什么是设计模式我们将会从设计模式的历史出发抽丝剥茧地分析设计模式这个概念。
注本系列文章不适合初学者需要至少掌握一门面向对象语言eg. C\Java\Golang\C#…并且本系列文章示例代码以C为主Java为辅因为网络上的博客只要提到设计模式一般都是Java而C的设计模式相对来说较少本文章所有的源码都有C和Java两个版本源码见白晨的Gitee仓库链接见下 设计模式: 白晨博客专栏《设计模式》源码 设计模式概述 一、设计模式的历史 设计模式的概念最早起源于建筑领域。哈佛大学的建筑学博士克里斯托弗.亚历山大是建筑学领域的模式之父。他与其研究团队用了约20年的时间对住宅和周边环境进行了大量的调查研究发现人们对舒适住宅和城市环境存在一些共同的认同规律将它们归纳成253个模式。 所以我们可以总结出模式的一个非常重要的特性可复用是模式的精髓。 在1994年由Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides四人合著出版了一本名为《Design Patterns - Elements of Reusable Object-Oriented Software》中文译名设计模式 - 可复用的面向对象软件元素的书该书首次提到了软件开发中设计模式的概念。这四位作者合称GOF四人帮全拼 Gang of Four。 他们所提出的设计模式主要是基于以下的面向对象设计原则对接口编程而不是对实现编程优先使用对象组合而不是继承。 总体来说设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想它强调降低依赖降低耦合。 虽然GoF距离现在已有近30年的历史部分设计模式也因为种种原因被时代所抛弃但是GoF中的大部分的设计模式依然被广泛的使用书中所提出的23种经典面向对象创立了模式在软件设计中的地位。
虽然GoF提出的设计模式被默认为“面向对象的设计模式”但是这并不意味着“设计模式”就等同于“面向对象的设计模式”。 二、理解面向对象 面向对象编程的三大特性是封装、继承和多态。这些特性是面向对象编程的基础它们使得代码更加模块化、可重用和可维护。 封装封装是指将数据和基于数据的操作封装在一起形成一个独立的实体。封装可以隐藏实现细节使得代码模块化。良好的封装能够减少耦合类内部的结构可以自由修改可以对成员进行更精确的控制隐藏信息实现细节。 继承继承是使用已存在的类的定义作为基础建立新类的技术。通过使用继承我们能够非常方便地复用以前的代码能够大大提高开发的效率。继承所描述的是“is-a”的关系如果有两个对象A和B若可以描述为“A是B”则可以表示A继承B。 多态多态是指同一操作作用于不同的对象上面时可以产生不同的解释和不同的执行结果。多态性分为编译时多态性和运行时多态性。编译时多态性又称为静态多态性主要实现方式是函数重载和运算符重载运行时多态性又称为动态多态性主要实现方式是虚函数。 作为一名程序员我们应该懂得从编程底层从微观角度去理解对象模型常见的底层思维有
语言构造编译转换内存模型运行时机制
如果说向下的底层思维是一个程序员必修课那么向上的抽象思维就是就是进化为一个优秀的程序员必备素养什么是抽象思维呢
简单地说就是将我们周围的事物转化成为代码的能力。
举个例子如果我们要将我们的学校抽象成代码首先我们需要大致划分出学校中的人员学生和老师其次要描述学校的建筑材料、建筑风格、具体布局最后要编写学校中的各种事件。
以上的过程老师、学生这样的人员可以抽象为对象学校的建材、建筑也可以抽象为对象发生事件更不用说为了方便管理和执行得有个统一的对象模型将其组织起来。
对象抽象出来以后要开始考虑如何实现这些对象是每个对象都写一个对象模型还是将其共同点提炼具体实现一个对象时继承这个共同对象。
最后要考虑代码的可维护性因为学校总是要装修、扩建的每年也都有毕业生和新生。如果代码设计不好可维护性较差就会出现牵一发而动全身的情况明明是一点小改动就要修改大部分代码所以选择合适的设计模式非常重要。
程序员常见的抽象思维有
面向对象组件封装设计模式架构模式 对于程序员来说底层思维和抽象思维相互依存共同决定一个程序员的上限。向下要能深入理解三大面向对象机制
封装隐藏内部实现继承复用现有代码多态改写对象行为
向上深刻把握面向对象机制所带来的抽象意义理解如何使用这些机制来表达现实世界掌握什么是“好的面向对象设计” 。 三、软件设计的复杂性 建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要失败。然而令人惊奇的是软件系统的用户在要求作出类似改变时却不会仔细考虑而且他们认为这只是需要简单编程的事。 ——Object-Oriented Analysis and Designwith Applications 为什么软件设计会变得复杂
根本原因是 变化 无论是什么原因引起的但是只要用户有需求就会有功能上的变化一旦要满足相应的功能我们的代码就得跟着变化。
所以我们应该如何解决由变化引起的复杂性呢
首先我们应该将大问题进行分解分解为一个个小问题也就是我们在编程中经常使用的——分而治之将复杂问题分解为独立的简单问题。其次我们应该从更高的维度去观察这个问题将其这个问题抽象成一类问题抓住其本质特征构建一个理想化或者说泛化的模型通过解决这个泛化模型的问题我们解决一类问题这也是一种复用逻辑。
所以软件设计的复杂性的一般性的解决方法为分解和抽象。
下面我们通过一个demo来理解上面内容假设我们要维护一个桌面绘图软件原代码见下Java版本原代码点击跳转
MainForm.cpp
class MainForm : public Form { // MainForm类继承自Form类
private:Point p1; // 鼠标按下时的点Point p2; // 鼠标抬起时的点vectorLine lineVector; // 存储所有线段的向量vectorRect rectVector; // 存储所有矩形的向量public:MainForm(){ // 构造函数//...}
protected:virtual void OnMouseDown(const MouseEventArgs e); // 鼠标按下事件处理函数virtual void OnMouseUp(const MouseEventArgs e); // 鼠标抬起事件处理函数virtual void OnPaint(const PaintEventArgs e); // 绘图事件处理函数
};void MainForm::OnMouseDown(const MouseEventArgs e){ // 鼠标按下事件处理函数实现p1.x e.X; // 记录鼠标按下时的x坐标p1.y e.Y; // 记录鼠标按下时的y坐标//...Form::OnMouseDown(e); // 调用父类的鼠标按下事件处理函数
}void MainForm::OnMouseUp(const MouseEventArgs e){ // 鼠标抬起事件处理函数实现p2.x e.X; // 记录鼠标抬起时的x坐标p2.y e.Y; // 记录鼠标抬起时的y坐标if (rdoLine.Checked){ // 如果选择了线段工具Line line(p1, p2); // 创建一个新的线段lineVector.push_back(line); // 将新线段添加到线段向量中}else if (rdoRect.Checked){ // 如果选择了矩形工具int width abs(p2.x - p1.x); // 计算矩形宽度int height abs(p2.y - p1.y); // 计算矩形高度Rect rect(p1, width, height); // 创建一个新的矩形rectVector.push_back(rect); // 将新矩形添加到矩形向量中}this-Refresh(); // 刷新窗体触发绘图事件Form::OnMouseUp(e); // 调用父类的鼠标抬起事件处理函数
}void MainForm::OnPaint(const PaintEventArgs e){ // 绘图事件处理函数实现for (int i 0; i lineVector.size(); i){ // 遍历所有线段并绘制e.Graphics.DrawLine(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.Graphics.DrawRectangle(Pens.Red,rectVector[i].leftUp,rectVector[i].width,rectVector[i].height);}Form::OnPaint(); // 调用父类的绘图事件处理函数
}Shape.h
// 点类定义包含x和y两个属性
class Point{
public:int x;int y;
};// 直线类定义包含起点和终点两个属性并有构造函数初始化这两个属性
class Line{
public:Point start;Point end;// 直线类构造函数初始化起点和终点属性Line(const Point start, const Point end){this-start start;this-end end;}
};// 矩形类定义包含左上角点、宽度和高度三个属性并有构造函数初始化这三个属性
class Rect{
public:Point leftUp;int width;int height;// 矩形类构造函数初始化左上角点、宽度和高度属性Rect(const Point leftUp, int width, int height){this-leftUp leftUp;this-width width;this-height height;}
};
下面是这段代码的主要执行逻辑
MainForm类是主窗体类继承自Form类。它有两个私有成员变量p1和p2分别用于存储鼠标按下和抬起时的坐标。它还有两个向量成员变量lineVector和rectVector分别用于存储所有的线段和矩形。当鼠标按下时会触发OnMouseDown事件处理函数。该函数会记录鼠标按下时的坐标即点p1。当鼠标抬起时会触发OnMouseUp事件处理函数。该函数会记录鼠标抬起时的坐标即点p2然后根据当前选择的工具线段或矩形创建相应的图形并添加到对应的向量中。在鼠标抬起事件处理函数中还会调用窗体的刷新方法即this-Refresh()这将触发绘图事件。OnPaint是绘图事件处理函数。当窗体刷新时会遍历所有的线段和矩形并在窗体上绘制出来。Point, Line, Rect是一些基础的图形类。其中点类包含x和y两个属性直线类包含起点和终点两个属性并有构造函数初始化这两个属性矩形类包含左上角点、宽度和高度三个属性并有构造函数初始化这三个属性。
现在要在原本绘制图形的基础上添加一个绘制“圆形”的功能
首先应该在Shape.h中添加一个 圆类 其次要修改MainForm.cpp中的MainForm类在其中添加一个存储所有圆形的数组 再者要修改MainForm.cpp中MainForm::OnMouseUp函数添加圆形工具的检测如果选择工具为圆形添加此圆形到数组 最后要修改MainForm.cpp中的MainForm::OnPaint函数添加圆形的绘制逻辑 由上可见我们为了添加一个圆形绘制功能至少要修改四处地方如果换成更加复杂的逻辑其中牵扯的类和交互会变得更多这就是由于变化所引起的软件设计的复杂性。
所以我们要来降低软件设计的复杂性首先我们想到的是分解但是分解面对现在的问题已经没有什么效果了因为每个对象都已经解决一个小任务了并且绘制功能也已经正常工作的现在要面对的是维护和进一步开发的问题。
所以我们现在需要抽象也即重新构筑代码结构使代码变得可维护性高、可拓展性高。
现在来观察一下原本的代码有什么问题
见下图MainForm依赖了一个经常变化的Shape.h或者说MainForm依赖了图形类的具体实现这样设计是非常差劲的一旦在Shape.h添加新图像类就必须更改MainForm中的多个成员导致MainForm更改过于频繁这是我们不想看到的我们理想中的MainForm应该是长期可以不用修改如果我们要增添绘制功能应该只用在Shape.h中添加新类即可。 古代的大禹治水告诉了我们一个经验方法堵不如疏。对于变化如果变化遍布多个类之间那么一旦发生变化连带的所有类都必须修改但是我们可以将变化疏导到个别类中使得变化只能影响个别类而不能对多个类造成影响。
观察现在的依赖MainForm是依赖于不稳定的Shape的如果要让MainForm保持稳定那么它必须依赖一个稳定的类。
什么类是稳定的呢一种是长时间保持不变的类另一种就是抽象类。长时间保持稳定的类在我们这个demo中是没有了但是我们可以给Shape.h中的具体类抽象出一个父类Shape让具体图形类全部继承这个Shape抽象类。 重构后的Shape.h
class Shape{
public:virtual void Draw(const Graphics g) 0; virtual ~Shape() { }
};class Point{
public:int x;int y;
};class Line: public Shape{
public:Point start;Point end;Line(const Point start, const Point end){this-start start;this-end end;}//实现自己的Draw负责画自己virtual void Draw(const Graphics g){g.DrawLine(Pens.Red, start.x, start.y,end.x, end.y);}};class Rect: public Shape{
public:Point leftUp;int width;int height;Rect(const Point leftUp, int width, int height){this-leftUp leftUp;this-width width;this-height height;}//实现自己的Draw负责画自己virtual void Draw(const Graphics g){g.DrawRectangle(Pens.Red,leftUp,width,height);}};//增加
class Circle : public Shape{
public:point center; int radius; Circle(const Point center, int radius){this-center center;this-radius radius;}//实现自己的Draw负责画自己virtual void Draw(const Graphics g){g.DrawCircle(Pens.Red,center,radius);}
};再来重构MainForm
首先将MainForm中的成员对象不能依赖具体类应该依赖抽象类 其次MainForm::OnPaint不应该依赖于具体实现应该让图像类自己绘制自己 最后还有MainForm::OnMouseUp依赖具体实现如果要消除MainForm::OnMouseUp中的变化可以使用我们后续文章所讲到的其他设计模式这里先按下不表。 重构后的MainForm.cpp
class MainForm : public Form {
private:Point p1;Point p2;//针对所有形状vectorShape* shapeVector;
public:MainForm(){//...}
protected:virtual void OnMouseDown(const MouseEventArgs e);virtual void OnMouseUp(const MouseEventArgs e);virtual void OnPaint(const PaintEventArgs e);
};void MainForm::OnMouseDown(const MouseEventArgs e){p1.x e.X;p1.y e.Y;//...Form::OnMouseDown(e);
}// 通过后续的学习也可以消除其变化这里先按下不表
void MainForm::OnMouseUp(const MouseEventArgs e){p2.x e.X;p2.y e.Y;if (rdoLine.Checked){shapeVector.push_back(new Line(p1,p2));}else if (rdoRect.Checked){int width abs(p2.x - p1.x);int height abs(p2.y - p1.y);shapeVector.push_back(new Rect(p1, width, height));}//改变else if (rdoCircle.checked){//...int radius (int)sqrt(pow(p2.x - p1.x, 2) pow(p2.y - p1.y, 2));shapeVector.push_back(new Circle(p1, radius));}//...this-Refresh();Form::OnMouseUp(e);
}void MainForm::OnPaint(const PaintEventArgs e){//针对所有形状for (int i 0; i shapeVector.size(); i){shapeVector[i]-Draw(e.Graphics); //多态调用各负其责}//...Form::OnPaint();
}重构后的代码的依赖关系变为 由上图可得MainForm和Line都依赖于Shape这个抽象类这个关系是稳定的变化被集中到Shape的具体实现中如果现在要添加一个绘制图像只需要在Shape.h中添加shape的子类即可这里假设MainForm::OnMouseUp中的变化也被消除。
这里要注意变化依然存在我们也不可能完全消除变化我们能做的只能是将变化集中管理。
我们可以从上面的demo中总结两条原则
高层模块(稳定)不应该依赖于低层模块(变化)二者都应该依赖于抽象(稳定) 。 MainForm不应该依赖于变化的具体图形类而应该依赖抽象的Shape类相应的具体的图像类也应该依赖于抽象的Shape类。 类模块应该是可扩展的但是不可修改。 发生变化时我们不应该过多的修改MainForm的具体实现细节如果经常修改其实现细节就得考虑代码的依赖关系是否出现了问题同样的Shape的结构也应该是不可修改的但是对于拓展也即实现其子类应该是被允许的。 这两条原则非常重要我们将会在下一篇文章中详细讲解设计模式的原则。 四、软件设计的目标 什么是好的软件设计软件设计的金科玉律 复用 正如我们前文提到的可复用是模式的精髓前人在面对软件设计的常见问题时抽象出了适用于同类问题的通用模式后人可以直接复用前人的模式进行设计减少了试错的成本加速了软件的开发这就是可复用的优势。
所以在这里我们可以给设计模式下个定义 设计模式是可复用的经典解决方案用于解决软件设计中的常见问题。 后记 在本文中我们探讨了设计模式的历史、面向对象编程的基本概念、软件设计的复杂性以及软件设计的目标。设计模式作为一种强大的工具可以帮助我们更好地应对日益复杂的软件开发挑战。
通过分解和抽象我们可以降低软件设计的复杂性提高代码的可维护性和可扩展性。设计模式的应用能够使我们的代码更具稳定性减少耦合提高代码的复用性以更加高效和灵活的方式开发软件。
下一篇中我们将着重探讨面向对象设计原则它是设计模式中最形而上的东西也是所有设计模式所要遵守的金科玉律。
如果大家喜欢这个系列还请大家多多支持啦
如果这篇文章有帮到你还请给我一个大拇指 和小星星 ⭐️支持一下白晨吧喜欢白晨【设计模式】系列的话不如关注白晨以便看到最新更新哟
我是不太能熬夜的白晨我们下篇文章见。 参考资料 《C设计模式》——李建忠 《HeadFirst设计模式第二版》 C多态_drogon c±CSDN博客