C++面向对象基础:设计模式(上)

何为设计模式

设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案;
设计模式可以认为是解决问题的固定套路,是满足设计原则的情况下,慢慢迭代出来的套路,学习设计模式,可以让我们深入理解面向对象的思想。

前置知识准备

想要学习设计模式,需要两方面的知识储备:

  1. 面向对象思想
  2. 设计原则

面向对象

众所不周知,面向对象的三大核心为封装, 继承和多态

  1. 封装:主要用于影藏实现细节,实现模块化
  2. 继承:旨在无需修改原有类的情况下通过继承来实现对功能的扩展。(注意C++的继承区别与其他面向对象语言,可以进行多继承)。
  3. 多态:面向对象最重要的特性,很多设计模式都依赖与多态来实现,其可以分为两种:(1)静态多态:主要是函数重载 (2)动态多态: 继承类中虚函数的重写

设计原则

对于设计原则,会在后面的设计模式中进行分析

依赖倒置原则

具体阐述为:高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖具体实现,具体实现应该依赖于抽象;
我们总应该使用一个抽象接口层,使得业务和具体实现解耦,高层模块不应该直接依赖于底层模块,接口的使用者不要依赖于具体的实现。

开放封闭原则

具体阐述为:对一个类应该对扩展(组合和继承)开放,对修改关闭。

面向接口原则

不将变量类型声明为某个特定的具体类,而是声明为某个接口.客户程序无需获知对象的具体类型,只需要知道对象所具有的接口.

封装变化点

将稳定点和变化点分离,扩展修改变化点;让稳定点和变化点的实现层次分离;

单一职责原则

一个类的职责应该尽可能的单一。

里氏替换原则

子类型必须能够覆盖其父类型,子类覆盖了父类方法的同时,也要能履行父类方法的职责。

借口隔离原则

不应该强迫客户依赖于他们不用的方法

组合优于继承

字面意思,组合的耦合度相比于继承往往更低。

最小知道原则

在客户能正常使用的情况下,应该保证客户对类的细节知道的越少越好。

创建型设计模式

模板方法模式

定义一个操作中的算法的骨架 ,而将一些步骤延迟到子类中。 Template Method使得子类可以不
改变一个算法的结构即可重定义该算法的某些特定步骤。 ——《 设计模式》

简单来说,就是我们如果希望某个方法的总体结构不改变,但方法执行的部分步骤能够变化,可以将这些步骤下放到子类中去执行。

示例需求

某个品牌动物园,有一套固定的表演流程,但是其中有若干个表演子流程可创新替换,以尝试迭代更新表演流程。

class ZooShow {
public:
    //单一职责原则,这个类就只用于Show
	//依赖倒置原则,子类扩展时,需要依赖基类的虚函数实现,客户使用时,也只依赖基类提供的接口
	//				不会和处于底层的子类接触	
    void Show() {
        if (Show0())
            PlayGame();
        Show1();
        Show2();
        Show3();
    }
    
// 接口隔离原则 不要让用户去依赖它们不需要的接口
// 最少知道原则 不让用户去关注类的实现细节
private:
    void PlayGame() {
        cout << "after Show0, then play game" << endl;
    }

protected:
    virtual bool Show0(){
        cout << "show0" << endl;
        return true;
    }
    virtual void Show2(){
        cout << "show2" << endl;
    }
    virtual void Show1() {

    }
    virtual void Show3() {

    }
};

//开闭原则,想要修改Show的调用步骤,不需要修改源代码,添加一个新的子类即可
//          即满足对扩展开放,对修改关闭

class ZooShowEx1 : public ZooShow {
protected:
    virtual bool Show0(){	//里氏替换原则,子类重写方法能够履行父类方法的职责
        cout << "show1" << endl;
        return true;
    }
    virtual void Show2(){
        cout << "show3" << endl;
    }
};

class ZooShowEx2 : public ZooShow {
protected:
    virtual void Show1(){
        cout << "show1" << endl;
    }
    virtual void Show2(){
        cout << "show3" << endl;
    }
};

如以上代码,使用模版方法模式,我们可以在保证Show方法总体结构不变的情况下,通过子类重写父类的protected方法,来改变Show的部分执行过程。

代码框架

  1. 基类中有骨架流程接口
  2. 所有子流程对子类开放(public或者protect方法),并且为虚函数
  3. 利用多态的方式来使用接口

观察者模式

定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所
有依赖于它的对象都得到通知并自动更新。 ——《 设计模式》

观察者模式用来处理一对多的依赖关系,用于实现一个方法的内容发生改变后,他的所有观察者的行为也会发生改变。

示例需求

气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到两个不同的显示终端(A和B);

//所有的终端继承自来抽象方法
class IDisplay {
public:
    virtual void Show(float temperature) = 0;
    virtual ~IDisplay() {}
};

class DisplayA : public IDisplay {
public:
    virtual void Show(float temperature);
private:
    void jianyi();
};

class DisplayB : public IDisplay{
public:
    virtual void Show(float temperature);
};

class DisplayC : public IDisplay{
public:
    virtual void Show(float temperature);
};

class WeatherData {
};

class DataCenter {
public:
	//封装变化点:这里通过增加终端和解绑终端来封装变化点
    void Attach(IDisplay * ob);     //用于将指定终端订阅该类
    void Detach(IDisplay * ob);     //将指定终端取消订阅该类
    void Notify() {                 //用于通知所有订阅的终端
        float temper = CalcTemperature();
        for (auto iter = obs.begin(); iter != obs.end(); iter++) {
            (*iter)->Show(temper);
        }
    }

// 接口隔离原则
private:
    virtual WeatherData * GetWeatherData();

    virtual float CalcTemperature() {           //获取气象站的资料,假定这个方法会经常改变
        WeatherData * data = GetWeatherData();
        // ...
        float temper/* = */;
        return temper;
    }
    //面向接口原则:将变量声明为接口类,只关注类的具体接口(Show方法)
    std::vector<IDisplay*> obs;     //用于保存所有订阅的终端
};

int main() {
    DataCenter *center = new DataCenter;
    IDisplay *da = new DisplayA();
    IDisplay *db = new DisplayB();
    IDisplay *dc = new DisplayC();
    center->Attach(da);
    center->Attach(db);
    center->Attach(dc);


    
    center->Notify();
    
    //-----
    center->Detach(db);
    center->Notify();
    return 0;
}

通过该设计模式,只要DataCenter类的CalcTemperature方法发生改变,我们都可以调用Nodify方法使得所有订阅了该类的终端发生变化,是否订阅也取决于客户,DataCenter不关心是否有类订阅了自身。扩展时,只需要新创建一个终端并继承自纯虚类,然后调用attach方法加入订阅即可。
同时,这里我们可以独立的改变目标和观察者,二者的耦合度较松。

策略模式

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。 ——《设计模式》

策略模式提供了一系列可重用的算法,客户程序对算法的调用关系是稳定的,类型在运行时可以根据需求在各个算法之间进行切换。

示例需求

某商场节假日有固定促销活动,为了加大促销力度,现提升国庆节促销活动规格;

class Context {

};

class ProStategy {
public:
    virtual double CalcPro(const Context &ctx) = 0;
    virtual ~ProStategy(); 
};


//开闭原则:扩展新的算法时,只需要重新编写一个类继承与ProStategy即可
class VAC_Spring : public ProStategy {
public:
    virtual double CalcPro(const Context &ctx){}
};

class VAC_QiXi : public ProStategy {
public:
    virtual double CalcPro(const Context &ctx){}
};


class VAC_QiXi1  : public VAC_QiXi {
public:
    virtual double CalcPro(const Context &ctx){}
};

class VAC_Wuyi : public ProStategy {
public:
    virtual double CalcPro(const Context &ctx){}
};

class VAC_GuoQing : public ProStategy {
public:
    virtual double CalcPro(const Context &ctx){}
};

class VAC_Shengdan : public ProStategy {
public:
    virtual double CalcPro(const Context &ctx){}
};

class Promotion {
public:
    Promotion(ProStategy *sss) : s(sss){}
    ~Promotion(){}
    double CalcPromotion(const Context &ctx){
        return s->CalcPro(ctx);
    }
private:
    //接口隔离原则:依赖注入,通过一个接口解决两个类的依赖
    //面向接口原则
    ProStategy *s;
};

int main () {
    Context ctx;
    ProStategy *s = new VAC_QiXi1();
    Promotion *p = new Promotion(s);
    p->CalcPromotion(ctx);
    return 0;
}

通过该设计模式,客户虽然一直调用的是CalcPromotion方法,但却可以根据p定义时的传参来改变CalcPromotion执行的算法。
扩展时,只需要新增一个类并继承自ProStategy,并重写ClacPro函数即可。

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值