Boolan C++设计模式 第一周笔记
来源:互联网 发布:加拿大进出口贸易数据 编辑:程序博客网 时间:2024/06/05 10:11
1.什么是设计模式
“每一个描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案。这样,你就能一次又一次地使用该方案而不必做重复劳动”。
——Christopher Alexander
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
我们在解决问题的时候,常常采取两种方式,一种是分解问题,另一种是抽象。
对于分解来说,其实就是分而治之,把大问题不断的划分为一个又一个的小问题,通过解决分解开的每一个小问题来解决整体的大问题。也就是不断的分工,各司其职来解决问题。
而抽象则属于更高的层次,从我们需要解决的问题出发,在与该问题相关的一组关联对象中提取出主要的或共有的部分――说简单一点,就是用相同的行为来操作不同的对象。
先来看看第一种方式解决问题的伪代码:
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; }};//增加class Circle{}; class MainForm : public Form {private:Point p1;Point p2;vector<Line> lineVector;vector<Rect> rectVector;//改变vector<Circle> circleVector;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){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);}//改变else if (...){//...circleVector.push_back(circle);}//...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);}//改变//针对圆形for (int i = 0; i < circleVector.size(); i++){e.Graphics.DrawCircle(Pens.Red,circleVector[i]);}//...Form::OnPaint(e);}
上面的伪代码可以用来解决点、直线和矩形的绘制,但是遇到绘制其他图形的时候就会遇到问题,必须对原伪代码进行繁琐的修改,具体修改不再进行。
如果使用另外一种方法,抽象的设计思路时,这个问题就会迎刃而解,伪代码如下:
lass 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 MainForm : public Form {private: Point p1; Point p2; //针对所有形状 vector<Shape*> 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 (...){ //... shapeVector.push_back(circle); } //... 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(e);}
从上面的伪代码可以看出,对每个形状增加了一个共同的父类Shape,同时在父类中定义了纯虚函数Draw,使得在多态调用的时候每个子类可以按照自己的方式来实现Draw函数。而界面类MainForm变成只需管理Shape的指针,不再是某个具体的对象,实现了向上抽象的管理。
如果这时候我们增加一个圆形的画法,那么我们只需在class Rect后面增加以下代码:
class Circle : public Shape{public: //实现自己的Draw,负责画自己 virtual void Draw(const Graphics& g){ g.DrawCircle(Pens.Red, ...); }};
通过以上的代码可以看出,当我们使用抽象思维对代码进行设计的时候,使得代码的变更更加容易,代码的复用性得到了提升,它是通过面向对象中的继承和多态性来实现。
作为一名程序员或者说是未来的程序员,抽象思维非常重要,甚至在某种程度上比底层思维更加重要,它在C++程序设计中主要包括以下几个部分:面向对象,组件封装,设计模式,架构模式。
2.面向对象设计原则
软件设计复杂的根本原因是因为它会遇到各种各样的变化:客户需求变化,技术平台变化,开发团队变化,市场环境变化等等。
抽象思维的最大特点是复用性,也就可以抵御这些变化。在1中我们谈到面向对象是抽象思维实现的基础,先让我们重新认识面向对象:
为了确保设计出来地系统具有抽象思维的特性,面向对象设计中提出了一系列的基本原则作为设计的思想。
(1)依赖倒置原则(DIP)
- 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。
- 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
(2)开放封闭原则(OCP)
- 对扩展开放,对更改封闭
- 模块应该是可扩展的,但是不可修改
(3)单一指责原则(SRP)
- 一个类应该仅有一个引起它变化的原因
- 变化的方向隐含着类的责任
- 子类必须能够替换他们的基类(is-a)
- 集成表达抽血类型
- 不应该强迫客户程序依赖他们不用的方法
- 接口应该小而完备
- 类继承通常为“白盒复用”,对象组合通常为“黑箱复用”
- 继承在某种程度上破坏了封装性,子类父类耦合度高
- 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低
- 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合
- 不讲变量类型声明为某个特定的具体类,而是声明为某个接口
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口
- 减少系统中个部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案
产业强盛的标志:接口的标准化
3. 23个设计模式
GOF-23模式分类
(1)从目的来看
- 创建型(Creational)模式:将对象,从而对应需求变化为对象创建时具体类型的实现引来的冲击。
- 结构型(Structural)模式:通过类继承或者对象组合的方式来获得更灵活的结构,从而应对需求变化为对象的结构带来的冲击
- 行为型(Behavioral)模式:通过类继承或者对象组合的方式,来划分类与对象的指责,从而应对需求变化为多个交互的对象带来的冲击。
- 类模式处理类与子类的静态关系
- 对象模式处理对象间的动态关系
从封装变化角度对模式分类
通过重构获得模式,我们来正确使用对应的设计模式。
推荐图书:重构-改善既有代码的设计、重构与模式。
重构关键技法:
- 静态绑定→动态绑定
- 早绑定→晚绑定
- 继承关系→组合关系
- 编译时依赖→运行时依赖
- 紧耦合→松耦合
“组件协作”模式:
- 现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
- 典型模式:Template Method、Strategy、 Observer / Event。
3.1 Template Method模式
动机(Motivation)
- 在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很大改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
- 如何在确定稳定操作结构额前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
下面以具体的代码为例进行学习,见代码3.1.1:
代码3.1.1
//程序库开发人员class Library{public:void Step1(){//...} void Step3(){//... } void Step5(){//... }};//应用程序开发人员class Application{public:bool Step2(){//... } void Step4(){//... }};int main(){Library lib();Application app();lib.Step1();if (app.Step2()){lib.Step3();}for (int i = 0; i < 4; i++){app.Step4();}lib.Step5();}
上述代码
Library开发人员需要开发1、3、5三个步骤,Application开发人员开发2、4两个步骤和程序主流程。这种通过应用端对流程框架进行调用的方式,称之为早绑定。
上述代码如何在确定稳定操作结构额前提下,来灵活应对各个子步骤的变化或者晚期实现需求呢?
请看3.1.2代码
代码3.1.2
//程序库开发人员class Library{public://稳定 template method void Run(){ Step1(); if (Step2()) { //支持变化 ==> 虚函数的多态调用 Step3(); } for (int i = 0; i < 4; i++){ Step4(); //支持变化 ==> 虚函数的多态调用 } Step5(); }virtual ~Library(){ }protected:void Step1() { //稳定 //..... }void Step3() {//稳定 //..... }void Step5() { //稳定//.....}virtual bool Step2() = 0;//变化 virtual void Step4() =0; //变化};//应用程序开发人员class Application : public Library {protected:virtual bool Step2(){//... 子类重写实现 } virtual void Step4() {//... 子类重写实现 }};int main(){ Library* pLib=new Application(); lib->Run();delete pLib;}}
上述代码在3.1.1的基础上做修改后,Library开发人员需要开发1、3、5三个步骤和程序主流程,Application开发人员只需开发2、4两个步骤。这种将流程放入开发库,由库开发者开发,提供给应用程序开发者调用的方法,称之为晚绑定。
在代码3.1.1中,存在以下两个问题:
第一,对应用程序开发人员来说,需要自己开发其中的2、4两个步骤,要求比较高,需要对库中的函数情况比较了解,而且重写的两个函数的难度也相对较大。
第二,库开发人员和应用程序开发人员开发的内容的耦合度很高,需要由用开发人员来组织整体调用流程,未来程序的扩展性和可维护性的难度都比较大。
而在代码3.1.2中,库开发人员对开发库进行了重构,增加量两个虚函数,定义了一个run函数,将之前的主流程放入,从而使流程稳定。同时库开发者不知道应用程序开发者会如何设计2、4步骤,因此将其定义为虚函数。
应用程序开发者只需在子类中重新定义这两个虚函数即可,不需要再去考虑整个流程的实现,有效的降低了开发难度。
模式定义
定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类中。Template Method 使得子类可以在不改变(复用)一个算法的结构即可重新定义(Override 重写)该算法的某些特定步骤。
——《设计模式》GoF
结构(Structure)
要点总结
- Template Method 是一种非常基础性的设计模式,在面向对象的系统中,有着大量的应用。他用最简洁的机制(虚函数的多态性)为很多应用程序的框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
- 除了可以灵活对应子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是 Template Method 的典型应用。
- 在具体实现方面,被 Template Method 调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),一般推荐将他们设置为 Protected 方法。
最近在忙导师项目,周末补上剩下的笔记...
- Boolan C++设计模式 第一周笔记
- Boolan 设计模式 第一周
- Swift第一周~ Boolan笔记
- [Boolan] C++第一周学习笔记
- Boolan IOS课程第一周 笔记
- C++面向对象第一周笔记<Boolan>
- Boolan* C++课程第一周笔记
- boolan面向对象编程 第一周笔记
- C++如何设计一个不含指针的类 (Boolan笔记第一周)
- GeekBand C++ 设计模式 第一周笔记
- GeekBand笔记-《C++设计模式》第一周
- [Boolan] C++第十一周 C++设计模式(一 )
- [Boolan]第一周学习笔记——rico风
- C++ 开发工程师 第一周笔记 boolan.com
- 【Boolan】C++面向对象编程培训第一周笔记
- Boolan STL与泛型编程 第一周笔记
- Boolan STL与泛型编程 第一周笔记
- Boolan博览网C++开发课程第一周笔记
- GBK,UTF-8,和ISO8859-1之间的编码与解码
- Dubbo和Dubbox简单使用
- Integer的自动拆装箱的陷阱(整型数-128到127的值比较问题)
- 琐碎BFS/DFS
- 微信开发内置浏览器JS自动关闭当前页面回到微信对话窗口
- Boolan C++设计模式 第一周笔记
- ViewPager+Fragment
- Java数组的三种初始化方式
- 如何在window上把你的项目提交到github
- mybatis
- JS本地对象之Array
- struts2(基础3)
- hibernate一对多|多对一关系---【小白系列】0基础到熟练应用hibernate框架(十二)
- 机器学习一些基本概念