C++设计模式之组件协作模式:Template Method、Strategy、Observer

“组件协作”模式:

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

  #典型模式: Template Method、 Strategy、 Observer / Event

part 1  Template Method 模版模式

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

  结构化软件设计流程

  

  面向对象软件设计流程

  

  早绑定与晚绑定

  

  晚绑定机制的实现方法:虚函数、函数指针(C中最常用,C++虚函数的底层原理也是函数指针)。

  (模版方法的)模式定义

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

  结构(UML图)

  

  代码实现

  稳定的代码写成非虚函数;变化的代码写成虚函数。

  Library(库)代码是相对稳定的,对照于上图的 AbrastractClss。代码示例如下:

class Library {
public:
    void run() { step1(); step2(); step3(); }
    ~Library() {};
protected:
    void step1() { cout << "step1" << endl; }; // 稳定的需求
    void step3() { cout << "step3" << endl; }; // 稳定的需求
    virtual void step2() = 0; // 变化的需求
};

  其中,step2() 函数是一些不稳定的需求,Library 不能预判出将来会出现的需求,所以交由子类去实现——定义为纯虚函数。

  Application 是根据新的需求,所编写的应对新变化的代码。代码示例如下:

class Application : public Library{
protected:
    virtual void step2() { cout << "Application::step2" << endl; }; // 根据不同需求,子类重写之
};

  其中,step2()函数是根据新的需求,把对策写在纯虚函数里,交由程序在运行时执行。

  客户端程序的调用如下:

int main() {
    Library* pLib = new Application();
    pLib->run();
    delete pLib;
    return 0;
}

  Q&A

  Q1:为什么基类的虚构方法是虚函数?

  A1:因为子类以指针的方式实现(Father* p = new Son() )构造和调用。所以当子类析构时,子类的析构函数才会被调用——父类的虚析构函数被子类的虚构函数重载(override)了。反之——如果父类的析构函数不定义为虚函数——以 Father* 型别实现的指针,在析构它的对象时,只会调用它本身的——Father类——虚构函数,这样会造成子类的内存泄漏。

  Q2:为什么Library类库的实现细节——step1()、step3()函数——定义为 protected ?

  A2: 因为这两个方法——step1()、step3()函数——对外界没什么关系,主要是与本类和子类有关系。

  要点总结

  #Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。

  #除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是Template Method的典型应用。

  #在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。

   #绝大多数软件框架中都有 Template Method 模式。

  #设计模式必须基于一个稳定点,如果全不稳定就不存在任何适用的设计模式了;反之,如果全稳定就不需要设计模式了。

  #Template Method 意为“样板”。run()方法就是样板,细节定义为纯虚函数,交由子类(用户)去定义。

part 2 策略模式 Strategy

  动机(Motivation)

  #在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。

  #如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?

  模式定义

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

  UML

  

  代码实现

  要实现一个计算税率的类。由于中国、美国以及将来可能会出现的各国之间的税率算法是不一样的,所以要采用一种设计模式来抵御变化(业务需求的变化)。从外层开始,一个类一个类的交代,直到内层核心的类。

  1. Context类,用于存储计算税率时要用到的“上下文”。

struct Context {
    // 为了简化代码,暂不定义计算税率时的上下文
};

  2. TaxStrategy类,为实现具体税率算法的上层抽象。定义的纯虚函数目的是实现运行时多态。

struct TaxStrategy {
    virtual double calculate(const Context& c) = 0;
    virtual ~TaxStrategy(); // 抽象基类的析构方法一定要定义为虚函数
};

  3. ChinaTax类,具体税率算法的实现类。

struct ChinaTax : public TaxStrategy{
    virtual double calculate(const Context& c) {
        return 1.0;
    }
};

  4. StrategyFactory 利用工厂方法模式实习了具体抽象类的动态创建。比如,可以生成ChinaTax类、America类对象等。

struct StrategyFactory{
    TaxStrategy* newStrategyFactory() {}
};

  5. SalesOrder类,属于客户端的调用——根据我对《大话设计模式》的理解。

struct SalesOrder {
    TaxStrategy *strategy;

    SalesOrder(StrategyFactory& sf){
        this->strategy = sf.newStrategyFactory();
    }

    ~SalesOrder() { delete this->strategy; };

    double calculateTax() {
        Context c;
        double val = strategy->calculate(c); // 多态调用
    }
};

  变化

  假设有一天需求增加了,需要增加对美国(America)的业务,所以税率算法需要增加对美国的支持。有了策略模式,只需让新增的类继承抽象基类 TaxStrategy ,重写虚函数 calculate() 即可!

// 拓展美国业务
// 客户需求的变化
struct AmericaTax : public TaxStrategy {
    virtual double calculate(const Context& c) {
        return 2.0;
    }
};

  要点总结

  #所谓设计模式和面向对象所讲的“复用”:编译单位、二进制层面的复用。当代码写完、编译、测试、部署之后原封不动,对需求变化比较稳定叫作复用;源代码级别的复制粘贴不叫复用。

  #Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。

  #Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。

  #如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。

part 3 观察者模式 Observer

  动机(Motivation)

  在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。

  使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

  应用

  解决了原代码破坏依赖倒置原则(DIP)——一般情况下,依赖关系是编译时依赖。

  接口(Interface)在C++中就是抽象基类。

  观察者模式在 Java 中称为 Listener ; C#中称为 Event。

  代码展示一 : from 《大话设计模式》

  1. 抽象的、稳定的类型:Subject、Observer。代码如下:

class Observer {
public:
    virtual void update() = 0;
};

class Subject {
public:
    void attach(Observer* observer) {
        _observers.push_back(observer);
    }

    void detach(Observer* observer) {
        _observers.remove(observer);
    }

    void notify() {
        for (auto &e : _observers)
            e->update();
    }
private:
    list<Observer*> _observers;
};

  2. 具体的、变化的类型:ConcreteSubject、ConcreteObserver。代码如下:

class ConcreteSubject : public Subject {
public:
    void setState(string state) {
        _state = state;
    }
    string getState() {
        return _state;
    }
private:
    string _state;
};

class ConcreteObserver : public Observer {
public:
    ConcreteObserver(ConcreteSubject* subject) {
        _subject = subject;
    }
    virtual void update() {
        _state = _subject->getState();
        cout << _state << endl;
    }
private:
    string _state;
    ConcreteSubject* _subject;
};

  3. 用户调用范例,代码如下:

int main() {
    ConcreteSubject* s = new ConcreteSubject();

    s->attach(new ConcreteObserver(s));

    s->setState("online!");
    s->notify();

    return 0;
}

  代码展示二 : from 《GoF 23》

  1. 稳定的、抽象的“上层”类:Subject、Observer。代码如下:

struct Subject; // 声明类型,不然 Observer 调用时会报错
struct Observer {
    virtual void update(Subject* sub) = 0;
    virtual ~Observer() {};
};

struct Subject {
    list<Observer*> _observers;

    void attach(Observer* observer) {
        _observers.push_back(observer);
    }

    void detach(Observer* observer) {
        _observers.remove(observer);
    }

    void notify() {
        for (auto &e : _observers)
            e->update(this);
    }

    virtual ~Subject() {};
};

  2.变化的、具体的类:ConcreteSubject、ConcreteObserver。代码如下:

struct ConcreteSubject : public Subject {
    string _subjectState;

    string getState() {
        return _subjectState;
    }
};

struct ConcreteObserver : public Observer {
    string _observerState;
    ConcreteSubject* _subject;

    ConcreteObserver(ConcreteSubject* concreteSubject) {
        _subject = concreteSubject;
        _subject->attach(this);
    }

    virtual void update(Subject* sub) {
        _observerState = _subject->getState();
        cout << _observerState << endl;
    }

    virtual ~ConcreteObserver() {
        _subject->detach(this);
    }
};

  3. 用户调用该模式代码范例:

int main() {

    ConcreteSubject* boss = new ConcreteSubject;
    ConcreteObserver* employee = new ConcreteObserver(boss);

    boss->_subjectState = "come back!";
    boss->notify();

    return 0;
}

  模式定义

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

  UML

  要点总结

  #使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。

  #目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。

  #观察者自己决定是否需要订阅通知,目标对象对此一无所知。

  #Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。

时间: 2024-10-05 13:30:25

C++设计模式之组件协作模式:Template Method、Strategy、Observer的相关文章

设计模式 ( 十九 ) 模板方法模式Template method(类行为型)

1.概述 在面向对象开发过程中,通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序.但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关.例子1:银行业务办理流程在银行办理业务时,一般都包含几个基本固定步骤:取号排队->办理具体业务->对银行工作人员进行评分.取号取号排队和对银行工作人员进行评分业务逻辑是一样的.但是办理具体业务是个不相同的,具体业务可能取款.存款或者转账. 2.问题 如何保证架构逻辑的正常执行,而不被子类破坏 ? 3.解决

设计模式 笔记 模版方法模式 Template Method

//---------------------------15/04/28---------------------------- //TemplateMethod 模版方法模式----类行为型模式 /* 1:意图: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.TemplateMethod使得子类可以不改变 一个算法的结构即可重定义该算法的某些特定步骤. 2:动机: 3:适用性: 1>一次性实现算法的不变的部分,并将可变的行为留给子类来实现. 2>各子类中的公共行为应被提取出来并集中

设计模式之二:组件协作模式

因为出现了 框架与应用程序的划分. 组件协作 模式通过晚期绑定,来实现框架和应用程序的松耦合,是二者之间协作时常有的模式. 典型模式: Template Method Strategy Observer / Event 一,Template Method 1,出现原因 原文地址:https://www.cnblogs.com/inspred/p/10976947.html

设计模式之模板方法模式(Template Method)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

设计模式 - 模板方法模式(template method pattern) JFrame 详解

模板方法模式(template method pattern) JFrame 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考模板方法模式(template method pattern): http://blog.csdn.net/caroline_wendy/article/details/32159455 模板方法模式(template method pattern), Java的JFrame使用模板方法模式, paint()是可以覆盖的方法,

设计模式 - 模板方法模式(template method pattern) Applet 详解

模板方法模式(template method pattern) Applet 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考模板方法模式(template method pattern): http://blog.csdn.net/caroline_wendy/article/details/32159455 模板方法模式(template method pattern), applet就是一个能够在网页上面执行的小程序, applet有很多钩子(

设计模式 - 模板方法模式(template method pattern) 排序(sort) 详解

模板方法模式(template method pattern) 排序(sort) 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考模板方法模式(template method pattern): http://blog.csdn.net/caroline_wendy/article/details/32159455 模板方法模式的一个主要的应用是排序(sort)算法. 对象的排列方式并不是完全相同, 所以需要排序(sort)算法compareTo()

设计模式 - 模板方法模式(template method pattern) 详解

模板方法模式(template method pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy 模板方法模式(template method pattern): 在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤. 模板方法可以进行挂钩(hook), 钩子(hook)是一种被声明在抽象类中的方法, 但只有空的或者默认的实现. 钩子的存在, 可以让子类有能力

设计模式 - 模板方法模式(template method pattern) JFrame 具体解释

模板方法模式(template method pattern) JFrame 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考模板方法模式(template method pattern): http://blog.csdn.net/caroline_wendy/article/details/32159455 模板方法模式(template method pattern), Java的JFrame使用模板方法模式, paint()是能够覆盖的方