GeekBand C++ Week9 Notes

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的指针,并且两个东西分别有各自的子类,向两个不同的方向延伸。

你可能感兴趣的:(GeekBand C++ Week9 Notes)