C++设计模式之模板模式(template)(行为型)

一 定义

模板方法模式是一种类的行为型模式,在它的结构图中只有类之间的继承关系,没有对象关联关系,模板方法模式(Template Method Pattern)官方定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

抽象模板中包含三种类型的方法: 基本方法模板方法钩子方法(Hook Method)。

基本方法——基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。

模板方法——核心方法,不允许子类重写,所以都会加上final修饰符,可以有一个或几个,一般是一个具体方法框架,按照固定的流程对基本方法的调度

钩子方法——为了让模板方法的执行结果的更好地适应因外界条件改变。比如说银行办理业务为例,办理业务是个模板方法,普通人要经历排队、取号、等待、办理四个基本流程,而Vip则不需要排队、取号、等待,那么在设计的时候我们的抽象模板类需要考虑到,于是乎你得需要在模板方法各基本方法调用之前增加条件判断,那么用于设置这个条件的方法就是钩子方法(Hook Method),钩子方法也可以是抽象的还可以由子类的一个方法返回值决定公共部分的执行结果。

二 ULM图

角色:

(1)AbstractClass:是抽象类,其实也就是一个抽象模板,定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的框架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。

(2)ConcreteClass:实现父类所定义的一个或多个抽象方法。每一个AbstractClass都可以有任意多个ConcreteClass与之对应,而每一个ConcreteClass都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。

总结:当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。我们通过模板方法模式,把这些行为搬移到单一的地方,这样帮助子类摆脱重复的不变行为的纠缠。

模板方法模式的优点

良好的扩展性,封装不变部分,扩展可变部分,把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。例如增加一个新的功能很简单,只要再增加一个子类,实现父类的基本方法就可以了。

提取公共部分代码,便于维护,减小维护升级成本,基本操作由父类定义,子类实现

基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原 则。

模板方法模式的缺点

通常抽象类是负责声明某一类的事物的共同属性和抽象方法,实现类则是完成定义具体的特性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响。每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高。

适合使用模板方法模式的场景

  1. 多个子类有公有的方法,并且逻辑基本相同时。
  2. 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个 子类实现。
  3. 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子方法约束其行为。

 四 实例

1. 填写简历表

最近有个招聘会,可以带上简历去应聘了。但是,其中有一家公司不接受简历,而是给应聘者发了一张简历表,上面有基本信息、教育背景、工作经历等栏,让应聘者按照要求填写完整。每个人拿到这份表格后,就开始填写。如果用程序实现这个过程,该如何做呢?一种方案就是用模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。我们的例子中,操作就是填写简历这一过程,我们可以在父类中定义操作的算法骨架,而具体的实现由子类完成。下面给出它的UML图。

 

#include <iostream>
#include <memory>

//简历
class Resume
{
protected: //保护成员
    virtual void SetPersonalInfo() {}
    virtual void SetEducation() {}
    virtual void SetWorkExp() {}
public:
    void FillResume()
    {
        SetPersonalInfo();
        SetEducation();
        SetWorkExp();
    }
};

class ResumeA: public Resume
{
protected:
    void SetPersonalInfo()
    {
        std::cout << "A's PersonalInfo" << std::endl;
    }
    void SetEducation()
    {
        std::cout << "A's Education" << std::endl;
    }
    void SetWorkExp()
    {
        std::cout << "A's Work Experience" << std::endl;
    }
};

class ResumeB: public Resume
{
protected:
    void SetPersonalInfo()
    {
        std::cout << "B's PersonalInfo" << std::endl;
    }
    void SetEducation()
    {
        std::cout << "B's Education" << std::endl;
    }
    void SetWorkExp()
    {
        std::cout << "B's Work Experience" << std::endl;
    }
};

int main()
{
    auto r1 = std::make_unique<ResumeA>();
    r1->FillResume();

    auto r2 = std::make_unique<ResumeB>();
    r2->FillResume();

    return 0;
}

 

五 模式的扩展

1)模板方法模式与控制反转(好莱坞原则)

在模板方法模式中,子类不显式调用父类的方法,而是通过覆盖父类的方法来实现某些具体的业务逻辑,父类控制对子类的调用,这种机制被称为好莱坞原则(Hollywood Principle),好莱坞原则的定义为:“不要给我们打电话,我们会给你打电话(Don‘t call us, we’ll call you)”。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。模板方法模式充分的体现了“好莱坞”原则。由父类完全控制着子类的逻辑,子类不需要调用父类,而通过父类来调用子类,子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。

2)模板方法模式符合开闭原则
模板方法模式意图是由抽象父类控制顶级逻辑,并把基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象的复用,同时也遵守了开闭原则。

父类通过顶级逻辑,它通过定义并提供一个具体方法来实现,我们也称之为模板方法。通常这个模板方法才是外部对象最关心的方法。在上面的银行业务处理例子中,templateMethodProcess这个方法才是外部对象最关心的方法。所以它必须是public的,才能被外部对象所调用。

子类需要继承父类去扩展父类的基本方法,但是它也可以覆写父类的方法。如果子类去覆写了父类的模板方法,从而改变了父类控制的顶级逻辑,这违反了“开闭原则”。我们在使用模板方法模式时,应该总是保证子类有正确的逻辑。所以模板方法应该定义为final的。所以AbstractClass类的模板方法templateMethodProcess方法应该定义为final。

模板方法模式中,抽象类的模板方法应该声明为final的。因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。

3)模板方法模式与对象的封装性

面向对象的三大特性:继承,封装,多态。

对象有内部状态和外部的行为。封装是为了信息隐藏,通过封装来维护对象内部数据的完整性。使得外部对象不能够直接访问一个对象的内部状态,而必须通过恰当的方法才能访问。

对象属性和方法赋予指定的修改符(public、protected、private)来达到封装的目的,使得数据不被外部对象恶意的访问及方法不被错误调用导造成破坏对象的封装性。

降低方法的访问级别,也就是最大化的降低方法的可见度是一种很重要的封装手段。最大化降低方法的可见度除了可以达到信息隐藏外,还能有效的降低类之间的耦合度,降低一个类的复杂度。还可以减少开发人员发生的的错误调用。

一个类应该只公开外部需要调用的方法。而所有为public方法服务的方法都应该声明为protected或private。如是一个方法不是需要对外公开的,但是它需要被子类进行扩展的或调用。那么把它定义为protected.否则应该为private。

显而易见,模板方法模式中的声明为abstract的基本操作都是需要迫使子类去实现的,它们仅仅是为模板方法服务的。它们不应该被抽象类(AbstractClass)所公开,所以它们应该protected。

因此模板方法模式中,迫使子类实现的抽象方法应该声明为protected abstract。

4)模板方法与勾子方法(hookMethod)

模板方法模式的抽象类定义方法:
模板方法:一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。
基本方法:基本方法是实现算法各个步骤的方法,是模板方法的组成部分。基本方法如下:
•抽象方法(Abstract Method)
•具体方法(Concrete Method)
•钩子方法(Hook Method):“挂钩”方法和空方法,
hook方法在抽象类中的实现为空,是留给子类做一些可选的操作。如果某个子类需要一些特殊额外的操作,则可以实现hook方法,当然也可以完全不用理会,因为hook在抽象类中只是空方法而已。
1)钩子方法的引入使得子类可以控制父类的行为。
2)最简单的钩子方法就是空方法,也可以在钩子方法中定义一个默认的实现,如果子类不覆盖钩子方法,则执行父类的默认实现代码。
3)比较复杂一点的钩子方法可以对其他方法进行约束,这种钩子方法通常返回一个boolean类型,即返回true或false,用来判断是否执行某一个基本方法。由子类来决定是否调用hook方法。

5) 小结

1)模板方法模式是一种类的行为型模式,在它的结构图中只有类之间的继承关系,没有对象关联关系。
2)板方法模式是基于继承的代码复用基本技术,模板方法模式的结构和用法也是面向对象设计的核心之一。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。
3)在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式。

 

发布了243 篇原创文章 · 获赞 37 · 访问量 25万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章