自学内容网 自学内容网

【组件协作】模板方法

模板方法

总体划分

设计模式的总体分类:
目的:

  • 创建型:解决对象创建的工作
  • 结构型:对象在需求变化对结构的冲击
  • 行为型: 多个类交互的责任的划分

范围:

  • 类模式处理类与子类的静态关系
  • 对象模式处理对象间的动态关系

从封装变化角度:

  • 组件协作类:协作问题
    • Template Method
    • Strategy
    • Observer/Event
  • 单一职责:类与类的责任划分问题
    • Decorator
    • Bridge
  • 对象创建:对象创建的依赖关系
    • Factory Method
    • Abstract Factory
    • Prototype
    • Builder
  • 对象性能:对象性能的优化问题
    • Singleton
    • Flyweight
  • 接口隔离
    • Facade
    • Proxy
    • Mediator
    • Adapter
  • 状态变化
    • Memento
    • State
  • 数据结构
    • Composite
    • Iterator
    • Chain of Responsibility
  • 行为变化
    • Command
    • visitor
  • 领域问题
    • Interpreter

重构

书籍推荐:

  • 《重构——改善既有代码的设计》
  • 《重构与模式》

设计模式的应用并非一蹴而就, 而是不断的重构, 其中的关键技法:

  • 静态->动态
  • 早绑定->晚绑定
  • 继承->组合
  • 编译时依赖->运行时依赖
  • 紧耦->松耦合

组件协作模式——模板方法

现代软件专业分工之后的第一个结果就是:框架和应用程序
组件协作模式通过晚绑定, 来实现框架与应用之间的松耦合

典型代表:

  • Template Method
  • Strategy
  • Observer/Event

当然, 并不是说其他的设计模式与组件协作无关, 而是想表达:这些模式体现明显

动机

在软件构建中, 常常有稳定的整体框架, 但各个子不走有很多改变的需求, 或者由于固有的原因
比如框架和应用之间的关系, 而无法和任务的整体结构同时实现

那么, 如何在确定稳定操作结构的前提下, 来灵活应对个子步骤的变化或者晚期实现需求

代码——做法一

库:
lib.cpp

class Library
{
public:
    void Step1()
    {
        // code
    }

    void Step3()
    {
        // code
    }

    void Step5()
    {
        // code
    }
};

应用层:

class Application
{
public:
    bool Step2()
    {
        // code
    }

    void Step4()
    {
        // code
    }
}

// 模拟的业务逻辑
int main()
{
    Library lib;
    Application app;

    lib.Step1();

    if (app.Step2())
    {
        lib.Step3();
    }

    for (size_t i = 0; i < 4; ++ i)
    {
        app.Step4();
    }

    lib.Step5();

    return 0;
}

代码——做法二

库:
lib.cpp

class Library
{
public:
    // 稳定 template method
    void Run()
    {
        Step1();

        if (Step2())
        {
            // 支持变化->虚函数多态调用
            Step3();
        }

        for (size_t i = 0; i < 4; ++ i)
        {
            // 支持变化->虚函数多态调用
            Step4();
        }

        Step5();
    }

    virtual ~Library()
    {
        // code
    }

protected:
    virtual void Step1()
    {
        // 稳定
        // code
    }

    virtual void Step3()
    {
        // 稳定
        // code
    }

    virtual void Step5()
    {
        // 稳定
        // code
    }

    virtual bool Step2() = 0;   // 变化
    virtual void Step4() = 0;   // 变化
}

应用程序开发:
main.cpp

class Application : public Library
{
protected:
    virtual bool Step2()
    {
        // 子类的重写实现
    }

    virtual void Step4()
    {
        // 子类的重写实现
    }
};

int main()
{
    Library *lib = new Application();
    lib->Run();
    delete lib;

    return 0;
}

对比

对于写法一
业务有五个步骤:
lib库完成1, 3, 5
业务开发人员完成:2, 4和程序主体

对于做法二:
lib库仍然完成1, 3, 5
但是lib库开发多完成了一个程序主流程

而Application开发人员完成:2, 4

调用关系:
方法一:Application开发人员调用库函数
方法二:Application开发人员调用程序主体(虚函数)

那么可以看出:
方法一:早绑定的做法, 为什么是早绑定

  1. 因为库写的早, Appcation的开发晚, 一个晚的东西调用早的东西就是早绑定

早绑定一直是面向过程时期的做法, 但是在面向对象这, 就有了晚绑定
虽然lib仍然是写的早, Application开发仍然是晚
但是lib反过来调用了Application, 也就是早的东西调用晚东西, 这个就叫晚绑定

因此就可以引出模板方法的定义:

定义

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

缺点

这个做法的Run必须得是稳定的, 因此如果Run不稳定, 则此设计模式不适用
这也是Template Method定义中提到的:一个操作中算法的骨架

那么还有一个有趣的点:
假设Step2和Step4都是稳定, 那么Template Method就没必要使用了, 反正都是稳定的
根本原因是因为设计模式的核心是在变化和稳定之间寻找分界点, 来分离和管理变化
将变化关在一个笼子里, 任由其在笼子里蹦跶, 但是不影响整个系统

总结

那么回过头来思考一下, 为什么要把程序的主体流程放在lib库里完成?
答案呼之欲出, 因为要稳定

但是这样也有弊端:
用方法一:业务开发人员不得不完成主流程, 其业务水平会得到很大的提升, 因为你不完成, 整个Application就无法实现
方法二:核心在父类里, 而业务开发人员不用写主流程, 甚至只要override几个虚函数就可以了

因此如果你是方法二的业务开发人员, 你会有一种只见树木不见森林的感觉, 因为你写的是子步骤,而不是核心流程

在Template Method中, 蕴含着一种“不要调用我, 我来调用你”的反向控制结构

其他

  1. 基类的析构函数要声明为虚函数
  2. 在面向对象中, 扩展一般为:继承+多态
  3. 如何实现晚绑定, 在面向对象中最基本和最基础的做法就是:虚函数, 但是在c++中还可以使用函数指针/函数对象等, 虽然虚函数的实现机制就是利用函数指针
  4. 被Template Method调用的函数可以有实现也可以没有实现, 一般声明和设计为protected

原文地址:https://blog.csdn.net/qq_51931826/article/details/140701691

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!