C++设计模式
为了理解松耦合设计思想,掌握面向对象设计原则
什么是设计模式?
是一种解决方案的核心,可以避免重复劳动
设计模式不等于面向对象设计模式
底层思维:向下,如何把握机器底层从微观理解对象构造
语言构造,变易转换
内存模型
运行时机制
抽象思维:向上,如何将我们的现实世界抽象为程序代码,
面向对象
组件封装
设计模式
架构模式
深入理解面向对象:
向下:封装,继承,多态
向上:深刻把握面向对象机制所带来的抽象意义,理解如何利用这些机制来表达现实世界,掌握什么是好的面向对象设计
如何解决复杂性?
分解,人们面对复杂性有一个常见的做法:分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题
抽象:更高层次来讲,人们处理复杂性有一个通用的技术,由于不能掌握全部的复杂对象,我们选择忽视它的非本质性细节而去处理泛化和理想化了的模型
class Point{
public:
intx;
int y;
};
class Line{
public:
Pointstart;
Pointend;
Line(constPoint& start, const Point& end, ){
This->start= start;
This->end= end;
}
};
抽象的过程
在shape里面有虚拟方法draw,一个形状负责画自己,实现自己的draw.
在子类中overide自己父类的虚函数
virtual void Draw(const Graphics& g){
g.DrawLine(Pens,Red, start.x, start.y, end.x, end.y);
}
class MainForm:public Form{
private:
pointp1;
point p2;
vector
shapeVector;//多态
public:
}
在后面对shapevector中的元素进行多态调用。
两种方法的区别,哪种更好?
客户需求的变化:
如果客户需要多加一个圆
//增加一个类
class Circle{
};
在mainform里增加一个
vector CircleVector
如果检测到要画圆则要判断将圆push——back到圆的vector里
然后刷新以后,要把圆显示出来
用抽象的方法,建立新的circle类
class Circle: public shape{
public:
//负责自己的draw
}
vector不需要动,因为是shape*指针
全都不用改变除了刷新
工厂模式里在刷新一个圆的时候也不需要改变
重用性得到了很高的提升
当需求变化的时候,更加方便
DRY!!!
由于不能掌握全部的复杂对象,处理泛化的问题
面向对象的设计原则
变化是复用的天敌,面向对象设计最大的优势在于抵御变化。
理解隔离变化
从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小。
各司其职
从微观层面来看,面向对象的方式更强调个各类的责任
在第一个例子里,画线的责任从mainform到了形状自己,接口一样但实际不一样。
对象是什么?
从语言实现层面来看,对象封装了代码和数据,
从规格层面来看,对象是一系列可以被使用的公共接口
从概念层面来看,
面向对象的设计原则//设计原则比模式更重要
依赖倒置原则(DIP)
高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)
抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
开放封闭原则(OCP)
对扩展开放,对更改封闭
类模块应该是可扩展的,但是不可以修改
单一职责原则(SRP)
一个类应该仅有一个引起它变化的原因
变化的方向隐含着类的责任
Liskov替换原则(LSP)
子类必须能够替换它们的基类(IS-A)
继承表达类型抽象
接口隔离原则(ISP)
不应该强迫客户程序依赖它们不用的方法
接口应该小而完备
优先使用对象组合,而不是类继承
类继承通常为白箱复用,对象组合通常为黑箱复用。
继承在某种程度上破坏了封装性,子类父类耦合度高
而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
封装变化点
使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良影响。
针对接口编程,而不是针对实现编程。
不将变量类型声明为具体的类,而是声明为某个接口,客户程序无需知道对象的具体类型,只需要知道对象所具有的接口。
减少系统中各部分的依赖关系,从而实现“高内聚,低耦合”类型的设计方案。
产业强盛的标志:接口标准化
模板方法
Template Method
GOF-23模式分类
设计模式的应用不应该先入为主,一上来就使用设计模式是对设计模式最大的误用,没有一步到位的设计模式。
重构的关键技法:
静态到动态,早绑定到晚绑定,继承到组合,编译时依赖到运行时依赖,紧耦合到松耦合
组件协作模式:
框架与应用程序的划分,组合协作模式通过晚期绑定,来实现框架和应用程序之间的松耦合,是二者之间协作时常用的模式。
典型模式:
template method
strategy
observer/event
动机:在软件构件过程中,某项任务常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因,比如框架和应用之间的关系,而无法和任务的整体机构同时实现
class library{
public:
voidstep1(){
//…
}
voidstep3(){
//…
}
void step5(){
}
};//程序库开发人员
class Application{
voidstep2{
}
void step4{
}
};
int main(){
Librarylib();
Applicationapp();
Lib.step1();
If(app.step2()){
Lib.step3();
}
….
}
另外一种做法:
库的开发人员
除了1,3,5,也写step2和step4
virtualbool step2(){}
virtualvoid step4(){}
把run()写在类里,1,3,5是protected,虚的析构函数。
子类重写实现
library* pLIb = new Apllication();
plib->run();
delete plib;
前一种方法lib开发人员开发1,3,5三个步骤,app开发人员开发2,4两个步骤,和程序主流程
另一种lib开发人员写1,3,5三个步骤和程序主流程,app开发人员开发2,4两个步骤。
第一种是app调用lib的,第二种是lib的调用app的
第一种写法是一种早绑定的写法,因为lib一般写的早,程序库写的早。晚的东西调用早的东西,但在面向对象语言中,有晚绑定的机制,lib写的早但是它调用app,所以是晚绑定。模式定义一个操作算法的骨架(稳定),而将一些步骤延迟到子类中,template method是的子类可以不改变一个算法结构即可以重定义override,重写该算法的某些特定步骤。
为什么叫template method?run就是一个样板
稳定中有变化,2,4支持变化,是虚函数的多态调用
c++中稳定的要写成非虚函数,变化的要写成虚函数
设计模式的假定条件是必须有一个稳定点
也一定有变化,设计模式的最大的作用就是在稳定和变化之间寻找一个平衡点,把兔子关进笼子里。
在具体实现方面,被template method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法,虚方法),但一般推荐把它们设置为protected方法。
“不要调用我,让我来调用你”的反向控制结构。
策略模式:
strategy策略模式是一个组件协作类的模式,实现框架和应用程序之间的松耦合
动机:在软件构件过程中,有些对象使用的算法可能多种多样经常改变,如果将这些算法都编码到对象中,将会使对象变得复杂,有时候支持不适用的算法也是一个性能负担,透明得更改,使算法和对象解耦。
Enum taxbase{
CN_Tax,
Us_tAX
dE_TAX
};
class SalesOrder{
texbasetax;
public:
doublecalculateTax(){
if(tax== cn_tax){
}
else if(tax ==us_tax){
}
else if(tax==de_tax){
}
}
};
有没有可能未来支持其他国家的税法
class taxStrategy{
public:
virtualdouble calculate(const context& context) = 0;
virtual~taxstrategy(){}
};
class CNTax:public taxstrategy{
public:
virtualdouble calculate(const context& context){}
};
class ustax: public taxstrategy{
public:
virtualdouble calculate(const context& context){}
};
…
class salesorder{
private:
taxstrategy*strategy;
public:
salesorder(strategyfactory*strategyfactory){
this->strategy= strategyfactory->newstrategy();
}
~salesorder(){}
public doublecalculatetax(){
contextcontext();
double val =
strategy->calculate(context);//多态调用
}
};
把一些列算法一个个封装起来并且使他们可以互相替换,是算法独立于客户程序(稳定)而变化(扩展,子类化)
strategy使类型在运行时方便地根据需要在各个算法之间切换
strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句实在解耦合。
如果Strategy对象没有实例变量,各个上下文可以共享一个strategy变量,从而节省对象开销。
有很多ifelse代码不会被真正使用但是要存在缓存里,使得有用的代码被挤到主存里,但这个不是主要的好处。
Observer/event观察者模式
动机:需要为某些对象建立一种通知依赖关系-一个对象的状态发生改变,所有依赖对象(观察者对象)都得到通知,如果这样的依赖关系很紧密,不能很好地抵御变化。
Class mainform:public forms{
Textbox*txtfilepath;
Texbox*txtfilenumber;
Public:
Voidbutton_click(){
Stringfilepath = txtfilepath->gettext();
Intnumber = atoi(txtfilenumber->gettext().c_str());
Filesplittersplitter(filepath, number);
Splitter.split();
}
};
class filesplitter{
stringm_filepath;
intm_filenumber;
public:
filesplitter(conststring& filepath, int filenumber):
{}
void split(){
//读取大文件
//分批次向小文件中写入
for(int I = 0; i
//…
}
}
};
需求是提供一个进度条,来展示完成进度
首先在maiform上有一个progressbar* progressbar成员
在file_splitter里也放一个progressbar
依赖:A依赖B,A在编译的时候只有B存在才能通过。
编译是依赖,
当我不需要用这个bar的时候会出问题,这个progressbar其实是一个通知。通知可以用抽象的方式来表达,而不是用一个控件
class IProgress{
public:
virtualvoid DoProgress(float value) = 0;
virtual~IProgress()
};
所以在filesplitter里就变成了
IProgress* m_Iprogress//抽象通知机制
If(m_Iprogress != nullptr){
M_Iprogress->DoProgress(i+1)/m_filenumber;
}
然后mainform多重继承Iprogress,C++一般用到多重继承都是一个基类和接口
装饰模式:
Decorator装饰模式
“单一职责模式”在软件组件设计中,如果责任划分不清晰,使用继承得到的结果往往会让子类急剧膨胀,同时充斥着重复代码,这时候关键要划清责任。
典型的模式有decorator和bridge。
Class stream{
Public:
Virtualchar Read(int number) = 0;
Virtualvoid seek(int position) = 0;
Virtualvoid write(char data) = 0;
Virtual~Stream(){}
};
class filestream : public stream{
};
class Networkstream: public stream{
};
我们需要对流的主体进行曹组偶以后才能加密。
Class CtyptoFileStream : public FileStream{
Public:
Virtualchar Read(int number){
FileStream:read(number);//读文件流
}
};
缓冲操作
//额外的加密操作
//额外的缓冲操作
这个设计的问题
最顶上是stream,被filestream, networkstream和memeorystream三种继承,然后每个分别有加密和缓冲的流
这样流就有很多,但其中有重复的代码
如何重构?
取消继承,把父类当做一个对象放入类中
然后再把各个父类做成多态,用基类来表示,会发现所有的类全都一样,只是运行的时候new出来的对象不一样。
但是要保证流的方法是虚方法,所以要继承自基类stream
桥模式:
由于某些类型的固有实现逻辑,使得他们有多个变化的维度
class messager(
public:
virtualvoid login
virtualvoid sendmessage
virtualvoid sendpicture
virtualvoid playsound
virtualvoid drawshape
virtualvoid writetext
virtual~message
);
我们还要支持PC平台和mobile平台的设计
class PCMessagerBase: public Messager{
public:
//重写后面的几个方法
};
class MobileMessagerBase: public Messager{
};
我们会发现在不同的平台上要支持不同的功能
class PCMessageLite: public PCmessagerBase{
};
class PCMessagerPerfect: publicPCMessagerBase{
};
class MobileMessagerLite: publicMobileMessagerBase{
};
后面的类可以用messager的多态来实现,然后发现后面的lite和perfect并没有重载前面的后面几个方法,所以要把messager拆分开成两个类。。
和decorator方法很像
如果子类里有重复的字段,都要往上提
abstraction和implementor,在abstraction里有一个implementor的指针,并且两个东西分别有各自的子类,向两个不同的方向延伸。