模板方法模式
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。在GOF95中模板方法被归类为行为模式。
- templateMethod为模板方法,它组织调用若干原语(Primitive)方法、具体方法和钩子方法,形成算法骨架:
1234567public void templateMethod(){primitiveOperation1();primitiveOperation2();concreteOperation();hook();} - primitiveOperation*为若干原语方法,在抽象类中作为抽象方法出现,因此模板方法与这些基本操作是解耦的
- 抽象类中可以包含 final concreteOperation() 这样的具体方法, 禁止覆盖,可以被模板或者子类直接调用
- 抽象类中可以包含 hook() 这样的钩子方法,默认什么都不做,子类可以覆盖它,从而在算法的不同点进行挂钩。如果子类的算法步骤是可选的,可以使用钩子方法代替抽象方法。钩子方法的命名一般是doXxx的形式,例如HttpServlet的doGet/doPost
- 具体类需要实现模板方法需要的所有原语方法
模板方法的优点:
- 实现代码复用:通过抽取子类的公共功能并放入到模板方法中实现复用
模板方法的缺点:
- 算法骨架不易于升级,应当注意仅把确定不会变化的部分放到模板方法中
模板方法的适用时机:
- 需要固定算法骨架的时候
- 需要抽取子类公共功能,避免代码重复时
- 需要控制子类的扩展点时
有闲的时候我喜欢自己炒菜吃,健康又实惠,家常菜的烹饪过程基本上是简单的套路:准备食材、烹调、装盘。要是能设计个机器女仆来帮我炒菜就好了,比起炒来我更喜欢吃:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class Maid { public: void cook( string foodType ) { if ( foodType == "鱼香肉丝" ) { cout << "葱白切丝" << endl; cout << "生姜切末" << endl; cout << "准备油盐酱醋" << endl; cout << "泡辣椒切末" << endl; cout << "猪里脊肉切细丝腌制" << endl; cout << "绿尖椒、胡萝卜、冬笋分别切细丝" << endl; cout << "锅中放少许油,放入葱、姜、蒜末炒香,放入泡辣辣末炒出红油" << endl; cout << "放入胡萝卜、冬笋、木耳翻炒2分钟,放入尖椒翻炒均匀" << endl; cout << "放入炒好的肉丝迅速翻炒均匀" << endl; count<< "装盘,主人请享用" << endl; } else if ( foodType == "凉拌黄瓜" ) { cout << "蒜捣成泥" << endl; cout << "准备油盐酱醋" << endl; cout << "黄瓜拍碎" << endl; cout << "调入酱油、蒜泥、醋搅拌" << endl; count<< "装盘,主人请享用" << endl; } else if(……){……} } }; |
女仆炒的菜味道不错,就是添加菜谱太麻烦了,我得不断添加else-if,不断的刷写女仆主板固件。
前面已经提到过了,炒菜基本上是三个步骤,我们家乡菜大部分都是准备葱姜蒜,切好肉、蔬菜,然后热油、炒制,最后装盘,既然算法步骤如此固定,何不引入模板方法模式,简化添加菜谱的难度呢?我决定引入可拔插的女仆芯片(MaidChip),每种芯片负责一种菜品:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
//厨房女仆芯片 class AbstractMaidChip { public: virtual void ~AbstractMaidChip() { } //炒菜,模板方法,规定了炒菜的基本步骤 void cook() { prepareFoodMaterial(); //准备食材,允许子类变更,提供了缺省适配的部分 doCook(); //烹调,子类动态扩展的部分 dishUp(); //起锅装盘,固定的算法部分 } private: void dishUp() { cout << "装盘,主人请享用" << endl; } protected: virtual void prepareFoodMaterial() { cout << "葱白切丝" << endl; cout << "生姜切末" << endl; cout << "准备油盐酱醋" << endl; } virtual void doCook() = 0; }; //鱼香肉丝芯片 class YuShiangShreddedPorkMaidChip : public AbstractMaidChip { protected: virtual void prepareFoodMaterial() { //继承通用步骤 AbstractMaidChip::prepareFoodMaterial(); //鱼香肉丝还要准备额外的食材: cout << "泡辣椒切末" << endl; cout << "猪里脊肉切细丝腌制" << endl; cout << "绿尖椒、胡萝卜、冬笋分别切细丝" << endl; } virtual void doCook() { cout << "锅中放少许油,放入葱、姜、蒜末炒香,放入泡辣辣末炒出红油" << endl; cout << "放入胡萝卜、冬笋、木耳翻炒2分钟,放入尖椒翻炒均匀" << endl; cout << "放入炒好的肉丝迅速翻炒均匀" << endl; } }; //凉拌黄瓜芯片 class saladCucumberMaidChip : public AbstractMaidChip { protected: virtual void prepareFoodMaterial() { cout << "蒜捣成泥" << endl; cout << "准备油盐酱醋" << endl; } virtual void doCook() { cout << "黄瓜拍碎" << endl; cout << "调入酱油、蒜泥、醋搅拌" << endl; } }; |
现在真是很方便呢,女仆主板也不要改动了,想吃新菜,只需要插一个新芯片就可以了:
1 2 3 4 5 6 7 8 9 10 |
class Maid { private: map<string, AbstractMaidChip*> chips; public: void cook( string foodType ) { chips[foodType]->cook(); } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static void sort(Object[] a) { Object[] aux = (Object[])a.clone(); mergeSort(aux, a, 0, a.length, 0); } //mergeSort本质上是模板方法 private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off) { int length = high - low; //合并排序的算法骨架 if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && //Comparable的compareTo本质上是原语方法 ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) swap(dest, j, j-1); //这是一个具体方法 return; } …… } |
这个例子和经典的模板方法模式在结构上差异很大,但是注意,模板方法模式的核心是:提供一个算法,并让子类型实现某些步骤。 Arrays.sort的例子中,合并排序的算法骨架已经建好,所有子类(Comparable的)必须实现合理的compareTo方法,以支持排序。
JFrame是Swing最基本的容器,其从java.awt.Component继承了一个paint()方法,默认paint什么都不做,它是一个钩子:
RepaintManager类的paintDirtyRegions方法是个模板方法,它会遍历所有过期的组件,依次调用其paint(),JFrame的子类可以覆盖paint以提供特定的绘制行为。
Spring框架中有大量的模板方法模式的实现,例如Bean生命周期管理,就是依靠钩子方法来实现的:
1 2 3 4 5 6 7 |
public class Bean { @PostConstruct public void init(){} //初始化钩子 @PreDestroy public void destory(){} //销毁钩子 } |
Spring中还包含了很多基于回调的模板方法变体,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class JdbcTemplate { //这是一个模板方法 public <T> T execute( StatementCallback<T> action ) throws DataAccessException{ //算法骨架 Connection conToUse = DataSourceUtils.getConnection( getDataSource() ); Statement stmt = conToUse.createStatement(); applyStatementSettings( stmt ); Statement stmtToUse = stmt; if ( this.nativeJdbcExtractor != null ){ stmtToUse = this.nativeJdbcExtractor.getNativeStatement( stmt ); } T result = action.doInStatement( stmtToUse ); //回调原语方法 handleWarnings( stmt ); return result; } } public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations { public <T> T browseSelected(final String queueName, final String messageSelector, final BrowserCallback<T> action) throws JmsException { return execute(new SessionCallback<T>() { public T doInJms(Session session) throws JMSException { //算法骨架 Queue queue = (Queue) getDestinationResolver().resolveDestinationName(session, queueName, false); QueueBrowser browser = createBrowser(session, queue, messageSelector); return action.doInJms(session, browser); //回调原语方法 } }, true); } } |
- 与工厂方法结合使用:原语方法返回创建的对象
- 基于回调技术的模板方法比起经典的基于继承的模板方法,更加灵活、耦合度更低,但是复杂度较高
- 模板方法与策略模式优点类似,但是前者的核心是算法骨架的封装;后者则是整个算法的封装
Leave a Reply