行为型设计模式

行为型设计模式

设计模式

  1. 定义:在软件开发中,经过验证的,用于解决特定环境之下,重复出现的特定问题的解决方案。设计模式在满足设计的原则的前提之下不断的迭代而来的。设计模式有其局限性,需要确定变化方向才使用,同时需要一定的工程代码量。

  2. 设计模式适合解决什么样的问题:既有稳定点也有变化点(全稳定或者全变化不使用(如游戏开发,使用的脚本语言)。是期望通过修改少量的代码来适应需求的变化,就如同整洁的房间中,有一只好动的猫,整洁的房间是稳定点,好动的猫是变化点,房间便是整个的代码结构,为了保持整个代码结构的稳定,需要约束这只猫(关起来)。笼子就是设计模式。

    Q:为什么说游戏开发时全变化或者变化较多,不使用设计模式

    A:首先,游戏开发是一个创造性的过程,要求创造新的、独特的体验和功能。游戏设计师和开发者通常需要频繁地修改和调整游戏的功能、规则和内容,以适应不断变化的需求和反馈。这种频繁的变化使得游戏开发过程中的设计模式难以应用。其次,游戏开发涉及到多个不同的领域,如图形渲染、物理模拟、人工智能、网络通信等。每个领域都有其特定的需求和挑战,需要采用不同的技术和方法来解决。因此,单一的设计模式可能无法适应所有领域和需求的变化。另外,游戏开发中的性能要求通常较高,需要高效地利用计算资源。某些设计模式可能会引入额外的开销和复杂性,影响游戏的性能。为了追求更好的性能和用户体验,开发者可能需要采用一些与传统设计模式不同的优化技巧和架构。尽管如此,设计模式在某些特定的情况下仍然可以在游戏开发中发挥作用。例如,在游戏引擎的开发中,一些经典的设计模式如单例模式、观察者模式等仍然有其价值。此外,一些游戏开发框架和工具也提供了特定的设计模式和最佳实践,可以帮助开发者更好地组织和管理游戏的结构和逻辑。

设计模式基础

面向对象思想

  1. 面向对象思想:即封装(模块化)继承(扩展)多态

  2. 多态:静态多态(函数重载),动态多态(虚函数重写)

  3. 虚函数中的早绑定和晚绑定:

    Parent p = new Son,如果parent没有virtual关键字,那么实现的是早绑定,也就是p会被强制转换成Parent类型的对象,那么调用的函数也就是Parent类的,如果有virtual关键字,那么就是晚绑定,p指向的就是Son对象,调用的函数也是根据虚函数表找到的Son对象的函数。*

设计原则

设计模式是设计原则演变过来的,符合设计原则的代码,只需要修改少量代码就可以演变成设计模式

依赖倒置原则

  1. 高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖于抽象

    image-2023052820481267

  2. 自动驾驶系统公司是高层,汽车生产厂商为低层,它们不应该互相依赖,一方变动另一方也会跟着变动;而应该抽象一个自动驾驶行业标准,高层和低层都依赖它;这样以来就解耦了两方的变动;自动驾驶系统、汽车生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象);

  3. 把PC电脑理解成是大的软件系统,任何部件如CPU、内存、硬盘、显卡等都可以理解为程序中封装的类或程序集,由于PC易插拔的方式,那么不管哪一个出问题,都可以在不影响别的部件的前提下进行修改或替换PC电脑里叫易插拔,面向对象里把这种关系:强内聚、松耦合

  4. 电脑里的CPU全世界也就是那么几家生产的,大家都在用,但却不知道Intel、AMD等公司是如何做出这个精密的小东西的。说明CPU的强内聚的确是强。但它又独自成为了产品,在千千万万的电脑主板上插上就可以使用,这是因为CPU的对外都是针脚式或触点式等标准的接口。这就是接口的最大好处。CPU只需要把接口定义好,内部再复杂我也不让外界知道,而主板只需要预留与CPU针脚的插槽就可以了。(高层模块不应该依赖低层模块,两者都应该依赖抽象:也就是电脑主板和CPU不能相互依赖,而是他们共同依赖针脚式或触点式等标准的接口;抽象不应该依赖细节,细节应该依赖于抽象:也就是这些针脚式的接口不能依赖CPU具体的实现细节,因为CPU内部的细节可能会变化的,而应该这些细节来依赖接口)

  5. 也就是要针对接口编程,不要对实现编程,无论主板、CPU、内存、硬盘都是在针对接口设计的,如果针对实现来设计,内存就要对应到具体的某个品牌的主板,那就会出现换内存需要把主板也换了的尴尬。PC电脑硬件的发展,和面向对象思想发展是完全类似的。这也说明世间万物都是遵循某种类似的规律,谁先把握了这些规律,谁就最早成为了强者。

  6. 面向过程的开发时,为了使得常用代码可以复用,一般都会把这些常用代码写成许许多多函数的程序库,这样我们在做新项目时,去调用这些低层的函数就可以了。比如我们做的项目大多要访问数据库,所以我们就把访问数据库的代码写成了函数,每次做新项目时就去调用这些函数。这也就叫做高层模块依赖低层模块.

  7. 问题也就出在这里,我们要做新项目时,发现业务逻辑的高层模块都是一样的,但客户却希望使用不同的数据库或存储信息方式,这时就出现麻烦了。我们希望能再次利用这些高层模块,但高层模块都是与低层的访问数据库绑定在一起的,没办法复用这些高层模块,这就非常糟糕了。就像刚才说的,PC里如果CPU、内存、硬盘都需要依赖具体的主板,主板一坏,所有的部件就都没用了,这显然不合理。反过来,如果内存坏了,也不应该造成其他部件不能用才对。而如果不管高层模块还是低层模块,它们都依赖于抽象,具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个的更改都不用担心其他受到影响,这就使得无论高层模块还是低层模块都可以很容易地被复用。这才是最好的办法。

  8. 依赖倒转其实可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计了[ASD]。

    可扩展

里氏替换

  1. 子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可能出现错误;覆盖了父类方法却没有实现父类方法的职责;

  2. 正因为有了这个原则,使得继承复用成为了可能,只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,子类也能够在父类的基础上增加新的行为。比方说,猫是继承动物类的,以动物的身份拥有吃、喝、跑、叫等行为,可当某一天,我们需要狗、牛、羊也拥有类似的行为,由于它们都是继承于动物,所以除了更改实例化的地方,程序其他处不需要改变。

    image-20230601211211257

  3. 也就是复写方法的时候,必须要能够实现父类的职责,这个默认的职责不能丢弃,如QT中的重写paintEvent,最后是要__super::paintEvent()。是要能够实现父类的职责的。

开放封闭原则

  1. 一个类应该对扩展(组合和继承)开放,对修改关闭

  2. 内存不够只要插槽足够就可以添加,硬盘不够可以用移动硬盘等,PC的接口是有限的,所以扩展有限,软件系统设计得好,却可以无限地扩展,即为开放封闭原则

  3. 迟到的确也不好,但不让迟到也不现实。家的远近,交通是否堵塞也不是可以控制的。其实迟到不是主要问题,每天保证8小时的工作量是老板最需要的,甚至8小时工作时间也不是主要问题,业绩目标的完成或超额完成才是最重要的指标,于是应该改变管理方式,比如弹性上班工作制,早到早下班,晚到晚下班,或者每人每月允许三次迟到,迟到者当天下班补时间等等,对市场销售人员可能就更加以业绩为标准,工作时间不固定了——这其实就是对工作时间或业绩成效的修改关闭,而对时间制度扩展的开放。这就需要老板自己很清楚最希望达到的目的是什么,制定的制度才最合理有效。

  4. 绝对的对修改关闭是不可能的。无论模块是多么的‘封闭',都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化[ASD]。”是很难预先猜测,但我们却可以在发生小变化时,就及早去想办法应对发生更大变化的可能。也就是说,等到变化发生时立即采取行动[ASD]

    在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化[ASD]。比如,我之前让你写的加法程序,你很快在一个client类中就完成,此时变化还没有发生。然后我让你加一个减法功能,增加功能需要修改原来这个类,这就违背了今天讲到的‘开放-封闭原则',于是就该考虑重构程序,增加一个抽象的运算类,通过一些面向对象的手段,如继承,多态等来隔离具体加法、减法与client耦合,需求依然可以满足,还能应对变化。这时又要再加乘除法功能,就不需要再去更改client以及加法减法的类了,而是增加乘法和除法子类就可。即面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码[ASD]。

    image-20230530225244727

面向接口原则

  1. 不将变量类型声明为某个特定的具体类,而是声明为某个接口;客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案;

封装变化点原则

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

单一职责原则

  1. 就一个类而言,应该仅有一个引起它变化的原因[ASD]。我们在做编程的时候,很自然地就会给一个类加各种各样的功能,比如我们写一个窗体应用程序,一般都会生成一个Form1这样的类,于是我们就把各种各样的代码,像某种商业运算的算法呀,像数据库访问的SQL语句呀什么的都写到这样的类当中,这就意味着,无论任何需求要来,你都需要更改这个窗体类,这其实是很糟糕的,维护麻烦,复用不可能,也缺乏灵活性。单一职责原则(SRP),就一个类而言,应该仅有一个引起它变化的原因。

  2. 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏

  3. 软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离[ASD]。其实要去判断是否应该分离出类来,那就是如果能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责[ASD],就应该考虑类的职责分离。

接口隔离

  1. 不应该强迫客户依赖于它们不用的方法;一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责;客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。
  2. 一种是类封装的时候通过限定词,public private protected。另一种是类与类的依赖,通过接口(依赖注入),或者容器存储接口

组合优于继承

  1. 继承耦合度高,组合耦合度低;

最小知道原则

设计模式分析思路

  1. 设计模式的变化点和稳定点
  2. 设计模式的代码结构
  3. 设计模式符合的设计原则
  4. 设计模式如何扩展代码
  5. 设计模式的应用场景

设计模式—模板方法

  1. 定义:一个操作中的算法的骨架 ,而将一些步骤延迟到子类中(变化点)。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤(不变点)。

  2. 子类可以复写父类子流程,使父类的骨架流程丰富;反向控制流程的典型应用;父类 protected 保护子类需要复写的子流程;这样子类的子流程只能父类来调用。本质:通过固定算法骨架来约束子类的行为;

  3. 模板方法模式是通过把不变行为搬移到超类,去除子类中的重复代码来体现它的优势。模板方法模式就是提供了一个很好的代码复用平台。因为有时候,我们会遇到由一系列步骤构成的过程需要执行。这个过程从高层次上看是相同的,但有些步骤的实现可能不同。这时候,我们通常就应该要考虑用模板方法模式了。当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。我们通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠。

    image-20230604220334468

  4. 不符合设计模式的代码

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

    #include <iostream>
    using namespace std;
    
    // 扩展方式:继承,多态组合(组合基类指针)
    
    //每次扩展,这个类都会被破坏
    class ZooShow {
    public:
        //依赖注入 传递的即使type
        ZooShow(int type = 1) : _type(type) {}
    
    public:
    
        void Show() {
            if (Show0())
                PlayGame();
            Show1();
            Show2();
            Show3();
        }
    
    //用户不应该单独调用以下方法 -- 接口隔离原则,最小知道原则
    private:
        void PlayGame() {
            cout << "after Show0, then play game" << endl;
        }
    
        //随着次数增加,这里代码会膨胀 -- 破坏单一职责原则,开闭原则
        bool Show0() {
            if (_type == 1) {
                // 
                return true;
            } else if (_type == 2 ) {
                //  ...
            } else if (_type == 3) {
    
            }
            cout << _type << " show0" << endl;
            return true;
        }
    
        void Show1() {
            if (_type == 1) {
                cout << _type << " Show1" << endl;
            } else if (_type == 2) {
                cout << _type << " Show1" << endl;
            } else if (_type == 3) {
    
            }
        }
    
        void Show2() {
            if (_type == 20) {
                
            }
            cout << "base Show2" << endl;
        }
    
        void Show3() {
            if (_type == 1) {
                cout << _type << " Show1" << endl;
            } else if (_type == 2) {
                cout << _type << " Show1" << endl;
            }
        }
    private:
        int _type;
    };
    
    
    int main () {
        ZooShow *zs = new ZooShow(1);
        zs->Show();
        return 0;
    }
    
    
  5. 符合设计模式的代码

    #include <iostream>
    using namespace std;
    
    // 开闭
    class ZooShow {
    public:
        void Show() {
            // 如果子表演流程没有超时的话,进行一个中场游戏环节;如果超时,直接进入下一个子表演流程
            if (Show0())
                PlayGame();
            Show1();
            Show2();
            Show3();
        }
        
    private:
        void PlayGame() {
            cout << "after Show0, then play game" << endl;
        }
    
        bool expired;
    
        // 对其他用户关闭,但是子类开放的,如果用户能访问,
        // 那么用户就会组合里面的调用,就违背了我们固定的流程
    protected:
    
        //多态
        virtual bool Show0() {
            cout << "show0" << endl;
            if (! expired) {
                return true;
            }
            return false;
        }
        virtual void Show2() {
            cout << "show2" << endl;
        }
        virtual void Show1() {
    
        }
        virtual void Show3() {
    
        }
    };
    
    // 模板方法模式 通过继承 每次迭代更新就只做扩展,不会修改到基类,是很稳定的
    
    //子类扩展的时候,需要依赖基类的虚函数实现
    //使用者也只需依赖接口,
    class ZooShowEx10 : public ZooShow {
    protected:
        virtual void Show0() {
            if (! expired) {
                return true;
            }
            return false;
        }
    }
    
    class ZooShowEx1 : public ZooShow {
    protected:
        virtual bool Show0() {
            // 里氏替换 这里一定是要能实现父类的职责的
            cout << "ZooShowEx1 show0" << endl;
            if (! expired) { 
                return true;
            }
            return false;
        }
        virtual void Show2(){
            cout << "show3" << endl;
        }
    };
    
    class ZooShowEx2 : public ZooShow {
    protected:
        virtual void Show1(){
            cout << "show1" << endl;
        }
        virtual void Show2(){
            cout << "show3" << endl;
        }
    };
    
    class ZooShowEx3 : public ZooShow {
    protected:
        virtual void Show1(){
            cout << "show1" << endl;
        }
        virtual void Show3(){
            cout << "show3" << endl;
        }
        virtual void Show4() {
            //
        }
    };
    /*
    */
    int main () {
    
        //最小知道原则,用户只需要知道Show接口
        ZooShow *zs = new ZooShowEx10; 
        // ZooShow *zs1 = new ZooShowEx1;
        // ZooShow *zs2 = new ZooShowEx2;
        zs->Show();
        return 0;
    }
    
    
    
  6. 代码结构:基类中有骨架流程接口,子流程对子类开放(protected)且是虚函数,子类复写虚函数

    //骨架流程接口
    public:
        void Show() {
            // 如果子表演流程没有超时的话,进行一个中场游戏环节;如果超时,直接进入下一个子表演流程
            if (Show0())
                PlayGame();
            Show1();
            Show2();
            Show3();
        }
    
    protected:
    
        //多态 开闭
        virtual bool Show0() {
            cout << "show0" << endl;
            if (! expired) {
                return true;
            }
            return false;
        }
        virtual void Show2() {
            cout << "show2" << endl;
        }
        virtual void Show1() {
    
        }
        virtual void Show3() {
    
        }
    
  7. 符合的设计原则

    • 单一职责原则

      基类是十分稳定的,只有一个功能,就是Show的骨架流程

    • 开闭原则

      整个基类是不允许修改的,只能对其进行扩展(继承)

    • 依赖倒置

      子类扩展的时候,需要依赖基类的虚函数实现,使用者也只需依赖接口,

    • 封装变换点

      通过protected实现对子流程(骨架流程中的变换点)的封装,变化的代码通过继承不断迭代

    • 接口隔离

      子流程,对其他用户关闭,但是子类开放的,如果用户能访问,那么用户可能会组合里面的调用,就违背了我们固定的流程。

    • 最小知道原则

      用户只需要知道Show接口即可。

    • 里氏替换重写的函数一定是要能实现父类的职责

      class ZooShowEx1 : public ZooShow {
      protected:
          virtual bool Show0() {
              
              cout << "ZooShowEx1 show0" << endl;
              if (! expired) { 
                  return true;
              }
              return false;
          }
          virtual void Show2(){
              cout << "show3" << endl;
          }
      };
      
  8. 扩展代码:只需要继承基类,复写虚函数,同时主函数中修改

    class ZooShowEx3 : public ZooShow {
    protected:
        virtual void Show1(){
            cout << "show1" << endl;
        }
        virtual void Show3(){
            cout << "show3" << endl;
        }
        virtual void Show4() {
            //
        }
    };
    
    int main () {
        ZooShow *zs = new ZooShowEx3; 
        zs->Show();
        return 0;
    }
    

设计模式—观察者模式

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

  2. 观察者模式使得我们可以独立地改变目标与观察者,从而使二者之间的关系松耦合;观察者自己决定是否订阅通知,目标对象并不关注谁订阅了观察者不要依赖通知顺序,目标对象也不知道通知顺序;常用在基于事件的ui框架中,也是 MVC 的组成部分;常用在分布式系统中、actor框架中;观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化

  3. 结构图

    image-20230730171354554

    image-20230730171427337

  4. 不符合设计模式的代码:

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

    class DisplayA {
    public:
        void Show(float temperature);
    };
    
    class DisplayB {
    public:
        void Show(float temperature);
    };
    
    class DisplayC {
    public:
        void Show(float temperature);
    }
    
    class WeatherData {
    };
    
    //不稳定点
    class DataCenter {
    public:
        void TempNotify() {
            DisplayA *da = new DisplayA;
            DisplayB *db = new DisplayB;
            DisplayC *dc = new DisplayC;
            // DisplayD *dd = new DisplayD;
            float temper = this->CalcTemperature();
            da->Show(temper);
            db->Show(temper);
            dc->Show(temper);
            dc->Show(temper);
        }
    private:
        float CalcTemperature() {
            WeatherData * data = GetWeatherData();
            // ...
            float temper/* = */;
            return temper;
        }
        WeatherData * GetWeatherData(); // 不同的方式
    };
    
    int main() {
        DataCenter *center = new DataCenter;
        center->TempNotify();
        return 0;
    }
    
    
  5. 符合设计模式的代码

    #include <list>
    #include <algorithm>
    using namespace std;
    //面向接口编程
    class IDisplay {
    public:
        virtual void Show(float temperature) = 0;
        virtual ~IDisplay() {}
    };
    
    class DisplayA : public IDisplay {
    public:
        virtual void Show(float temperature) {
            cout << "DisplayA Show" << endl;
        }
    private:
        void jianyi();
    };
    
    class DisplayB : public IDisplay{
    public:
        virtual void Show(float temperature) {
            cout << "DisplayB Show" << endl;
        }
    };
    
    class DisplayC : public IDisplay{
    public:
        virtual void Show(float temperature) {
            cout << "DisplayC Show" << endl;
        }
    };
    
    
    
    class WeatherData {
    };
    
    // 应对稳定点,抽象
    // 应对变化点,扩展(继承和组合)
    class DataCenter {
    public:
        //封装变化点:应对终端的增加和删除
        void Attach(IDisplay * ob) {
            //添加
        }
        void Detach(IDisplay * ob) {
            //删除
        }
    
        //不变的地方:一对多的这种变化
        void Notify() {
            float temper = CalcTemperature();
            for (auto iter : obs) {
                iter.Show(temper);
            }
        }
    
    // 接口隔离
    private:
        WeatherData * GetWeatherData();
    
        float CalcTemperature() {
            WeatherData * data = GetWeatherData();
            // ...
            float temper/* = */;
            return temper;
        }
    
        //接口隔离,解耦合类与类的隔离:容器存储接口
        std::list<IDisplay*> obs;
    };
    
    int main() {
        // 单例模式
        DataCenter *center = new DataCenter;
        // ... 某个模块
        IDisplay *da = new DisplayA();
        center->Attach(da);
    
        // ...
        IDisplay *db = new DisplayB();
        center->Attach(db);
        
        IDisplay *dc = new DisplayC();
        center->Attach(dc);
    
        center->Notify();
        
        //-----
        center->Detach(db);
        center->Notify();
    
    
        
        return 0;
    }
    
  6. 代码结构

    class IDisplay {
    public:
        virtual void Show(float temperature) = 0;
        virtual ~IDisplay() {}
    };
    
    class DataCenter {
    public:
        void Attach(IDisplay * ob) {
            //添加
        }
        void Detach(IDisplay * ob) {
            //删除
        }
        
        void Notify() {
            float temper = CalcTemperature();
            for (auto iter : obs) {
                //.....
            }
        }
    
    // 接口隔离
    private:
        std::list<IDisplay*> obs;
    };
    
    
  7. 符合的设计原则

    • 面向接口编程

      观察者类是抽象成接口的,他们都有一个共同的功能,观察者直接继承这个虚基类

    • 接口隔离

      类中的限定词隔离,用户不应该能调用某些接口。类与类之间隔离:这里通过一个list的容器接口,进行了解耦合。

    • 封装变化点

      attach和detach对应的是这个“多”的变化点的增加和减少

  8. 扩展代码

    //增加一个具体的观察者类,继承接口
    class DisplayD : public IDisplay{
    public:
        virtual void Show(float temperature) {
            cout << "DisplayD Show" << endl;
        }
    };
    
    //通知类调用:相应的增加和减少
    center->Attach(dd);
    center->Notify();
    
  9. 缺点:尽管已经用了依赖倒置原则,但是“抽象通知者”还是依赖”抽象观察察者,它不一定是”更新”的方法要调用呀。比如我可能需要不同的方法,需要使用不同的函数实现。也就是通知者和观察者互不知道,由客户端来决定通知谁。通过事件委托机制

    ps:来自《大话设计模式》

    委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。一个委托可以搭载多个方法,所有方法被依次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。'delegate void EventHandler ();',可以理解为声明了一个特殊的‘类'。而'publicevent EventHandler Update;'可以理解为声明了一个事件委托的变量叫‘更新’。'new EventHandler (tongshil.CloseStockMarket)'其实就是一个委托的实例,而它就等于将‘tongshil.CloseStockMarket'这个方法委托给'huhansan.Update'这个方法了。这里委托给了这个eventhandler

    image-20230730173144926

    image-20230730173034869

    image-20230730173120182

设计模式—策略模式

  1. 定义:定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。稳定点:客户程序与算法的调用关系,变化点:算法的改变(新增或者变化)

  2. 策略模式提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换;策略模式消除了条件判断语句;也就是在解耦合;

  3. 不符合设计模式的代码

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

    enum VacationEnum {
    	VAC_Spring,
        VAC_QiXi,
    	VAC_Wuyi,
    	VAC_GuoQing,
        VAC_ShengDan,
    };
    
    class Promotion {
        VacationEnum vac;
    public:
        double CalcPromotion(){
            if (vac == VAC_Spring {
                // 春节
            }
            else if (vac == VAC_QiXi) {
                // 七夕
            }
            else if (vac == VAC_Wuyi) {
                // 五一
            }
    		else if (vac == VAC_GuoQing) {
    			// 国庆
    		}
            else if (vac == VAC_ShengDan) {
    
            }
         }
        
    };
    
  4. 符合设计模式的代码

    class Context {
    
    };
    
    // 稳定点:抽象去解决它
    // 变化点:扩展(继承和组合)去解决它
    class ProStategy {
    public:
        virtual double CalcPro(const Context &ctx) = 0;
        virtual ~ProStategy(); 
    };
    // cpp
    class VAC_Spring : public ProStategy {
    public:
        virtual double CalcPro(const Context &ctx){}
    };
    // cpp
    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){}
    };
    // cpp
    class VAC_Wuyi : public ProStategy {
    public:
        virtual double CalcPro(const Context &ctx){}
    };
    // cpp
    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;
    }
    
  5. 代码结构

    class Context {
    
    };
    
    // 稳定点:抽象去解决它
    // 变化点:扩展(继承和组合)去解决它
    class ProStategy {
    public:
        virtual double CalcPro(const Context &ctx) = 0;
        virtual ~ProStategy(); 
    };
    
    
    //两个类的关系:隔离使用依赖注入
    //这个类是稳定的
    class Promotion {
    public:
        Promotion(ProStategy *sss) : s(sss){}
        ~Promotion(){}
        double CalcPromotion(const Context &ctx){
            return s->CalcPro(ctx);
        }
    private:
        ProStategy *s;
    };
    
    
    Context ctx;
    ProStategy *s = new VAC_QiXi1();
    Promotion *p = new Promotion(s);
    p->CalcPromotion(ctx);
    
    
  6. 符合的设计原则

    • 接口隔离:通过一个接口解决两个类的依赖关系(依赖注入)
    • 面向接口:算法的变化继承自虚基类
    • 开闭原则:算法的抽象(扩展),稳定类的修改关闭。
  7. 扩展代码

    class VAC_Shengdan : public ProStategy {
    public:
        virtual double CalcPro(const Context &ctx){}
    };
    
    Context ctx;
    ProStategy *s = new VAC_QiXi1();
    Promotion *p = new Promotion(s);
    p->CalcPromotion(ctx);
    
posted @ 2023-06-01 21:23  vacancyl  阅读(84)  评论(0)    收藏  举报