当前位置: 首页 > news >正文

4、“组件协作“模式

目录

4.1 Template Method模式(模板方法模式)

4.1.1 动机(Motivation)

4.1.2 结构化软件设计流程

4.1.3 面向对象软件设计流程

4.1.4 早绑定与晚绑定

4.1.5 模式定义

4.1.6 结构

4.1.7 要点总结

4.2 Strategy 策略模式

4.2.1 动机(Motivation)

4.2.2 模式定义

4.2.3 代码示例

4.2.4 结构

4.2.5 要点总结

4.3 Observer 观察者模式

4.3.1 动机(Motivation)

4.3.2 模式定义

4.3.3 代码示例

4.3.4 结构

4.3.5 要点总结




  • 现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
  • 典型模式
    • Template Method
    • Strategy
    • Observer / Event

4.1 Template Method模式(模板方法模式)

4.1.1 动机(Motivation)

  • 在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
  • 如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?

4.1.2 结构化软件设计流程

template1_lib.cpp

//template1_lib.cpp
//程序库开发人员
class Library{
public:
    void Step1() { 
        //... 
    }
    
    void Step3() { 
        //... 
    }
    
    void Step5() { 
        //... 
    }
};

template1_app.cpp

//template1_app.cpp
//应用程序开发人员
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();
    
    return 0;
}

4.1.3 面向对象软件设计流程

template2_lib.cpp

//template2_lib.cpp
//程序库开发人员
class Library {
public:
    //稳定 template method
    void Run() {
        Step1();
        
        if (Step2()) { //支持变化==> 虚函数的多态调用
            Step3();
        }
        
        for (int i = 0; i < 4; i++) {
            Step4(); //支持变化==> 虚函数的多态调用
        }
        
        Step5();
    }
    //基类中的析构函数要写成虚函数,否则delete的时候的调用不到子类的析构函数
    virtual ~Library() { }
    
protected:
    void Step1() { //稳定
        //...
    }
    
    void Step3() { //稳定
        //...
    }
    
    void Step5() { //稳定
        //...
    }
    
    virtual bool Step2() = 0; //变化
    virtual bool Step4() = 0; //变化
};

template2_app.cpp

//template2_app.cpp
//应用开发人员
class Application : public Library {
protected:
    virtual bool Step2() {
        //... 子类重写实现
    }
    
    virtual bool Step4() {
        //... 子类重写实现
    }
};

//规范的
int main() {
    Library* pLib = new Application(); //pLib是个多态指针
    pLib->Run();
    
    delete pLib; 
}

4.1.4 早绑定与晚绑定

【注:结构化软件设计的流程是一种早绑定的写法,Library写的比Application早,写得比较晚的调用实现比较早的程序就叫做早绑定;面向对象软件设计的流程是一种晚绑定的写法,Library反过来调用Application,实现的比较早的调用实现比较晚的就叫做晚绑定;】


4.1.5 模式定义

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

​ ——《设计模式》GoF

【注:此处的“骨架”对应于上面的第二种写法中的Run,“延迟到子类”的意思就是定义虚函数让子类去实现或重写,就是支持子类来变化。

第二种写法中的模板方法就是Run,它是相对稳定的,但是它其中又包含了变化(Step2和Step4)。如果极端地讨论,全部是稳定的或者全部是变化的都不适合使用设计模式。

模式应用的核心就是分辨出变化和稳定。】

4.1.6 结构

image-20211213160414002
AbstractClass中的TemplateMethod()是稳定的,*PrimitiveOperationX()*是变化的。设计模式的学习重点就是区分开“稳定”和“变化”的部分。

对应到之前的代码实现:AbstractClass就是Library类;TemplateMethod()就是Run()方法;PrimitiveOperation1() 对应于 Step2()方法; PrimitiveOperation2() 对应于 Step4() 方法;ConcreteClass就是Application类;

4.1.7 要点总结

Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点【注:扩展点就是继承+虚函数】,是代码复用方面的基本实现结构。
除了可以灵活应对子步骤的变化外,==“不要调用我,让我来调用你”==的反向控制结构 是Template Method的典型应用。
在具体实现方面,被Template Method 调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为 protected 方法。

4.2 Strategy 策略模式

4.2.1 动机(Motivation)

在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?

4.2.2 模式定义

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。

​ ——《设计模式》GoF

4.2.3 代码示例

strategy1.cpp

//strategy1.cpp
enum TaxBase {
    CN_Tax,
    US_Tax,
    DE_Tax,
    FR_Tax       //更改
};

class SalesOrder{
    TaxBase tax;
public:
    double CalculateTax(){
        //...
        
        if (tax == CN_Tax){
            //CN***********
        }
        else if (tax == US_Tax){
            //US***********
        }
        else if (tax == DE_Tax){
            //DE***********
        }
        else if (tax == FR_Tax){  //更改
            //...
        }

        //....
     }
    
};
//说明:这种方式更改的时候违反了开放封闭原则

strategy2.cpp

//strategy2.cpp
class TaxStrategy{
public:
    virtual double Calculate(const Context& context)=0;
    virtual ~TaxStrategy(){}
};

//规范的写法是每个类放在不同的文件中

class CNTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class USTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class DETax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};


//扩展:正常应该是在一个新的文件中写,此处只是为了方便演示
//*********************************
class FRTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //.........
    }
};


class SalesOrder{
private:
    TaxStrategy* strategy; //抽象类,必须放一个指针,而且具有多态性

public:
    SalesOrder(StrategyFactory* strategyFactory){
        this->strategy = strategyFactory->NewStrategy();
    }
    ~SalesOrder(){
        delete this->strategy;
    }

    public double CalculateTax(){
        //...
        Context context();
        
        double val = 
            strategy->Calculate(context); //多态调用
        //...
    }
    
};

//说明:这种方式扩展的时候遵循了开放封闭原则

复用指的是编译后二进制意义的复用,而不是简单的代码片段的复用。SalesOrder是稳定的,各种XXTax 是变化的。

4.2.4 结构

image-20211213162328458
【注:Context和Strategy 是稳定的,ConcreteStrategyX 是变化的】

 

4.2.5 要点总结

Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。【注:绝对稳定不变的情况可以用if-else,如一周七天,而其他的可能变化条件判断的情况就要用Strategy模式。代码示例中的第一种写法的很多条件判断代码可能根本不会执行,但是却被迫装载到了CPU高级缓存中,占用了缓存位置,其他代码可能被挤出高级缓存,不得不装载到硬盘;而第二种写法则不会有这个问题,减轻了性能负担,但这不是Strategy模式的最大优势,Strategy模式的最大优势是用扩展应对变化。看到条件判断的情况,都要考虑能不能使用Strategy模式。】
如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。【注:一般可以使用单例模式】

4.3 Observer 观察者模式

4.3.1 动机(Motivation)

在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

4.3.2 模式定义

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

​ ——《设计模式》GoF

4.3.3 代码示例

实现一个文件分割器

第一种方案:

FileSplitter1.cpp

//FileSplitter1.cpp
class FileSplitter
{
    string m_filePath;
    int m_fileNumber;
    ProgressBar* m_progressBar; //注:ProgressBar是实现细节,容易变化。 是个通知控件

public:
    FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
        m_filePath(filePath), 
        m_fileNumber(fileNumber),
        m_progressBar(progressBar){

    }

    void split(){

        //1.读取大文件

        //2.分批次向小文件中写入
        for (int i = 0; i < m_fileNumber; i++){
            //...
            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            m_progressBar->setValue(progressValue); //更新进度条
        }

    }
};

MainForm1.cpp

//MainForm1.cpp
class MainForm : public Form
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
    ProgressBar* progressBar;

public:
    void Button1_Click(){

        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());

        FileSplitter splitter(filePath, number, progressBar);

        splitter.split();

    }
};


存在的问题:违背了DIP原则,如果A依赖于B——编译时“依赖”,即A编译的时候B要存在。

重构使得遵循DIP原则:

FileSplitter1.cpp

//FileSplitter1.cpp
class IProgress{
public:
    virtual void DoProgress(float value)=0;
    virtual ~IProgress(){}
};

class FileSplitter
{
    string m_filePath;
    int m_fileNumber;
    //ProgressBar* m_progressBar; //注:ProgressBar是实现细节,容易变化。 是个通知控件
    IProgress* m_iprogress;
public:
    FileSplitter(const string& filePath, int fileNumber, IProgress* iprogress;) :
        m_filePath(filePath), 
        m_fileNumber(fileNumber),
        m_iprogress(iprogress){

    }

    void split(){

        //1.读取大文件

        //2.分批次向小文件中写入
        for (int i = 0; i < m_fileNumber; i++){
            //...
            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            m_iprogress->DoProgress(progressValue); //更新进度条
        }
    }
};

MainForm2.cpp

//MainForm2.cpp
class MainForm : public Form, public IProgress
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
    ProgressBar* progressBar;

public:
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());
        FileSplitter splitter(filePath, number, this);
        splitter.split();
    }
    
    virtual void DoProgress(float value) {
        progressBar->setValue(value);
    }
};


进一步的小优化:

FileSplitter1.cpp

//FileSplitter1.cpp
class IProgress{
public:
    virtual void DoProgress(float value)=0;
    virtual ~IProgress(){}
};

class FileSplitter
{
    string m_filePath;
    int m_fileNumber;
    //ProgressBar* m_progressBar; //注:ProgressBar是实现细节,容易变化。 是个通知控件
    IProgress* m_iprogress;
public:
    FileSplitter(const string& filePath, int fileNumber, IProgress* iprogress;) :
        m_filePath(filePath), 
        m_fileNumber(fileNumber),
        m_iprogress(iprogress){

    }

    void split(){

        //1.读取大文件

        //2.分批次向小文件中写入
        for (int i = 0; i < m_fileNumber; i++){
            //...
            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            onProgress(progressValue); 
        }
    }
protected:
    virtual void onProgress(float value) {
        if (m_iprogress != nullptr) {
            m_iprogress->DoProgress(value);//更新进度条
        }
    }
};


目前的实现只能支持一个观察者,此处就是MainForm。

修改使得支持多个观察者:

FileSplitter2.cpp

//FileSplitter2.cpp
class IProgress{
public:
    virtual void DoProgress(float value)=0;
    virtual ~IProgress(){}
};


class FileSplitter
{
    string m_filePath;
    int m_fileNumber;

    List<IProgress*>  m_iprogressList; // 抽象通知机制,支持多个观察者
    
public:
    FileSplitter(const string& filePath, int fileNumber) :
        m_filePath(filePath), 
        m_fileNumber(fileNumber){

    }


    void split(){

        //1.读取大文件

        //2.分批次向小文件中写入
        for (int i = 0; i < m_fileNumber; i++){
            //...

            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            onProgress(progressValue);//发送通知
        }
    }

    void addIProgress(IProgress* iprogress){
        m_iprogressList.add(iprogress);
    }

    void removeIProgress(IProgress* iprogress){
        m_iprogressList.remove(iprogress);
    }


protected:
    virtual void onProgress(float value){
        List<IProgress*>::iterator itor = m_iprogressList.begin();
        while (itor != m_iprogressList.end() )
            (*itor)->DoProgress(value); //更新进度条
            itor++;
        }
    }
};

MainForm2.cpp

//MainForm2.cpp
class MainForm : public Form, public IProgress
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;

    ProgressBar* progressBar;

public:
    void Button1_Click(){

        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());

        ConsoleNotifier cn;

        FileSplitter splitter(filePath, number);

        splitter.addIProgress(this); //订阅通知
        splitter.addIProgress(&cn); //订阅通知

        splitter.split();

        splitter.removeIProgress(this);

    }

    virtual void DoProgress(float value){
        progressBar->setValue(value);
    }
};

class ConsoleNotifier : public IProgress {
public:
    virtual void DoProgress(float value){
        cout << ".";
    }
};


 

4.3.4 结构

image-20211213163605233
【注:Observer对应于IProgress,Update()对应于DoProgress(),Attach对应于addIProgress,Detach对应于removeIProgress,Notify对应于onProgress, GOF中建议将这三个方法提出来放到一个父类中,其他的Subject继承它,但是此处我们没有将它提出来,ConcreteSubject就是FileSplitter,ConcreteObserver对应于MainForm和ConsoleNotifier,具体的观察者。

稳定的:Subject、Observer

变化的:ConcreteSubject、ConcreteObserver

4.3.5 要点总结

  • 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
  • Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。

————————————————
版权声明:本文为CSDN博主「明朗晨光」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011386173/article/details/122062803

相关文章:

  • 刚开始做自媒体,无从下手,有什么好的建议吗?
  • 2022 华为杯研赛F题思路 研究生数学建模
  • Opencv项目实战:12 你这背景太假啦!
  • python解CCF-CSP真题《202209-1 如此编码》
  • 数据分析可视化08 案例 2:历史数据变化趋势图设计
  • Redis-缓存击穿
  • 信息学奥赛一本通:2072:【例2.15】歌手大奖赛
  • 【Linux】进程控制 (万字)
  • ARMv9新特性:虚拟内存系统架构 (VMSA) 的增强功能
  • 【JavaSE】之流程控制与方法
  • SpringCloud——网关1
  • 『Android基础入门』ViewPager+Fragment+BottomNavigationView实现底部导航
  • Regmap子系统:(寄存器映射)
  • 用通俗易懂的方式讲解:lightGBM 算法及案例(Python 代码)
  • TC8:TCP_CONTROL_FLAGS_05-08
  • 【译】JS基础算法脚本:字符串结尾
  • JavaScript 如何正确处理 Unicode 编码问题!
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Java|序列化异常StreamCorruptedException的解决方法
  • SpringBoot 实战 (三) | 配置文件详解
  • vue自定义指令实现v-tap插件
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 关于Flux,Vuex,Redux的思考
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 聊一聊前端的监控
  • 免费小说阅读小程序
  • 扑朔迷离的属性和特性【彻底弄清】
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 数据可视化之 Sankey 桑基图的实现
  • 为什么要用IPython/Jupyter?
  • 小程序开发之路(一)
  • 写给高年级小学生看的《Bash 指南》
  • ionic入门之数据绑定显示-1
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • #NOIP 2014# day.2 T2 寻找道路
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (12)目标检测_SSD基于pytorch搭建代码
  • (6)STL算法之转换
  • (二)WCF的Binding模型
  • (二十四)Flask之flask-session组件
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (转)VC++中ondraw在什么时候调用的
  • ... 是什么 ?... 有什么用处?
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .equals()到底是什么意思?
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .net对接阿里云CSB服务
  • :=
  • [].slice.call()将类数组转化为真正的数组
  • [20180312]进程管理其中的SQL Server进程占用内存远远大于SQL server内部统计出来的内存...
  • [C++]AVL树怎么转