定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
示例一:汽车模型
1. 类图说明:悍马车有两个型号,H1和H2。按需求,只需要给出悍马车模型。此处有一个抽象类,然后两个不同型号的模型实现类,通过简单继承就可以实现业务需求。
2. 结构说明:
HummerModel 抽象类,在类中定义了发动、停止、鸣笛、引擎、跑几个方法。根据不同型号来进行不同的实现。
3. 汽车模型,类图10-1
4. 汽车模型,代码清单10-1:
//////// ********** 1.抽象悍马模型 ,代码清单10-1:***************//
#include <QCoreApplication>
#include <QDebug>
class HummerModel
{
protected:
virtual void start() = 0;
virtual void stop() = 0;
virtual void alarm() = 0;
virtual void enginBoom() = 0;
public:
virtual void run()
{
this->start();
this->enginBoom();
this->alarm();
this->stop();
}
};
class HummerH1Model:public HummerModel
{
protected:
virtual void start() {qDebug() << "HummerH1 : start";}
virtual void stop() {qDebug() << "HummerH1 : stop";}
virtual void alarm() {qDebug() << "HummerH1 : alarm";}
virtual void enginBoom() {qDebug() << "HummerH1 : enginBoom";}
};
class HummerH2Model:public HummerModel
{
protected:
virtual void start() {qDebug() << "HummerH2 : start";}
virtual void stop() {qDebug() << "HummerH2 : stop";}
virtual void alarm() {qDebug() << "HummerH2 : alarm";}
virtual void enginBoom() {qDebug() << "HummerH2 : enginBoom";}
};
int main()
{
HummerModel *h1 = new HummerH1Model();
HummerModel *h2 = new HummerH2Model();
h1->run();
h2->run();
return 0;
}
示例二:
1. 模板方法模式通用类图,类图10-3
2. 类图说明:AbstractClass 叫抽象模板,他的方法分为两类:
基本方法:即基本操作,是由子类实现的方法,并且在模板方法被调用。
模板方法:可以有一个或几个,一般是一个具体方法,即一个框架,实现对基本方法的调度,完成固定的逻辑
注:为了防止被恶意操作,C++中可以使用两种方式
- 模板方法最后加上final关键字,不允许被覆写
- 模板方法为非虚函数,不允许被重载
类图中的 ConcreteClass1 和 ConcreteClass2 属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。
3. 模板方法模式,代码清单10-2:
////////// ********** 2.抽象模板 ,代码清单10-2:***************//
#include <QCoreApplication>
#include <QDebug>
class AbstractClass
{
protected:
virtual void doSomething() = 0; //基本方法
virtual void doAngthing() = 0;
public:
void templateMethod() //模板方法
{
this->doSomething();
this->doAngthing();
}
};
class ConcreteClass1:public AbstractClass
{
protected:
virtual void doSomething() {qDebug() << "ConcreteClass1 : doSomething";}
virtual void doAngthing() {qDebug() << "ConcreteClass1 : doAngthing";}
};
class ConcreteClass2:public AbstractClass
{
protected:
virtual void doSomething() {qDebug() << "ConcreteClass2 : doSomething";}
virtual void doAngthing() {qDebug() << "ConcreteClass2 : doAngthing";}
};
int main ()
{
AbstractClass *class1 = new ConcreteClass1();
AbstractClass *class2 = new ConcreteClass2();
class1->templateMethod();
class2->templateMethod();
delete class1;
delete class2;
return 0;
}
注:抽象模板中的基本方法尽量设计为 protected 类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为 protected 类型。实现类若非必要,尽量不要扩大父类中的访问权限。
三、工厂方法模式的应用
优点:
- 封装不变部分,扩展可变部分。例如,汽车模型中,增加一个H3型号的模型,增加一个子类,实现父类的基本方法就可以。
- 提取公共部分代码,便于维护。
- 行为由父类控制,子类实现。
缺点:
一般设计习惯,抽象类负责声明最抽象的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法点到了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,在复杂的项目中,会带来代码阅读的难度。
使用场景:
- 多个子类有共有的方法,并且逻辑基本相同时。
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边相关细节功能由各个子类实现。
- 重构时,把相同代码抽取到父类中,通过钩子函数(见“模板方法模式的扩展”)约束其行为。
四、模板方法模式的扩展
示例三: 汽车模型,增加需求 H1的喇叭是否响可控,H2的喇叭不响
汽车模型扩展,类图10-4:
类图说明:
在抽象类 HummerModel 中增加了一个实现方法 isAlarm,确定各个型号的车是否需要声音,由各个实现类覆写该方法,同时其他基本方法由于不需要对外提供访问,因此也设计为 protected 类型
汽车模型扩展,代码清单10-1:
////////// ********** 3.抽象悍马模型扩展 ,代码清单10-3:***************//
class HummerModel
{
protected:
virtual void start() = 0;
virtual void stop() = 0;
virtual void alarm() = 0;
virtual void enginBoom() = 0;
virtual bool isAlarm() {return true;}
public:
void run()
{
this->start();
this->enginBoom();
if (this->isAlarm())
{
this->alarm();
}
this->stop();
}
};
class HummerH1Model:public HummerModel
{
public:
void setAlarm(bool OK){this->m_alarmFlag = OK;}
protected:
virtual void start() {qDebug() << "HummerH1 : start";}
virtual void stop() {qDebug() << "HummerH1 : stop";}
virtual void alarm() {qDebug() << "HummerH1 : alarm";}
virtual void enginBoom() {qDebug() << "HummerH1 : enginBoom";}
virtual bool isAlarm() {return this->m_alarmFlag;}
private:
bool m_alarmFlag;
};
class HummerH2Model:public HummerModel
{
protected:
virtual void start() {qDebug() << "HummerH2 : start";}
virtual void stop() {qDebug() << "HummerH2 : stop";}
virtual void alarm() {qDebug() << "HummerH2 : alarm";}
virtual void enginBoom() {qDebug() << "HummerH2 : enginBoom";}
virtual bool isAlarm() {return false;}
};
int main()
{
HummerH1Model h1;
HummerH2Model h2;
h1.setAlarm(true);
h1.run();
h2.run();
return 0;
}
五、最佳实践
提问:父类是否可以调用子类方法?
回答:能,但是不建议这么做,合适的3个方式如下:
- 把子类传递到父类的有参构造中,然后调用。
- 使用反射的方式调用。
- 父类调用子类的静态方法。
以上方法可以解决问题,但不建议用父类调用子类方法。
合适的方法:
父类建立框架,子类重写了父类部分方法后,再调用从父类继承的方法,产生不同的结果,即模板方式。
开元框架中的使用:
开元框架提供了一个抽象类,然后有一堆子类,如果需要扩展功能,可以继承这个抽象类,然后覆写 protected 方法,然后调用一个类似 execute方法,即可扩展。
参考文献《秦小波. 设计模式之禅》(第2版) (华章原创精品) (Kindle 位置 308-310). 机械工业出版社