模板方法模式定义
定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
介绍
在软件开发中,有时会遇到类似的情况,某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用模板方法模式来应对这类情况。
模式中的角色包括:
AbstractClass:抽象类,定义了一套算法。
ConcreteClass:具体实现类。
角色UML图如下:
以烧菜为例:
是否有食材=》没有则需要购买食材,有就不需要购买=》烧菜=》吃饭=》意外情况
UML图如下:
java代码:
抽象算法框架:
package demo1;
/**
*
* @ClassName: AbstractCooking
* @Description: 做饭抽象
* @author cheng
* @date 2017年8月14日 下午9:25:31
*/
public abstract class AbstractCooking {
/**
* 具体方法
* @Title: cooking
* @Description:烧菜方法。该方法为final,防止算法框架被复写
*/
public final void cooking(){
System.out.println("我来看看还有没有食材");
if(!isHave()){
System.out.println("哇,好惨,没有食材了");
buyFood();
}else{
System.out.println("幸好,还有很多食材");
}
System.out.println("开始烧菜啦");
eat();
doSomething();
}
/**
* 抽象方法
* @Title: buyFood
* @Description:购买食材
*/
protected abstract void buyFood();
/**
* 具体方法
* @Title: eat
* @Description:吃饭
*/
protected void eat(){
System.out.println("吃饭");
}
/**
* 钩子方法
* @Title: isHave
* @Description: 是否有食材,默认没有
* @return
*/
protected boolean isHave(){
return false;
}
/**
* 钩子方法
* @Title: doSomething
* @Description:意外情况
*/
protected void doSomething(){}
}
需要注意的是这个抽象类包含了三种类型的方法,分别是抽象方法、具体方法和钩子方法。
* 抽象方法是交由子类去实现,
* 具体方法则在父类实现了子类公共的方法实现。
* 钩子方法则分为两类,第一类,它有一个空实现的方法,子类可以视情况来决定是否要覆盖它;第二类,这类钩子方法的返回类型通常是bool类型的,一般用于对某个条件进行判断,如果条件满足则执行某一步骤,否则将不执行。
具体实现类1:
package demo1;
/**
*
* @ClassName: ZhangSanCooking
* @Description: 张三烧菜
* @author cheng
* @date 2017年8月14日 下午9:38:24
*/
public class ZhangSanCooking extends AbstractCooking {
/**
* 复写
*/
@Override
protected void buyFood() {
System.out.println("我是个吃货,我要买很多很多的食材");
}
/**
* 复写
*/
@Override
protected void doSomething() {
System.out.println("一边看电视一边吃饭");
}
}
具体实现类2
package demo1;
/**
*
* @ClassName: LiSiCooking
* @Description: 李四烧菜
* @author cheng
* @date 2017年8月14日 下午9:43:15
*/
public class LiSiCooking extends AbstractCooking {
/**
* 复写
*/
@Override
protected void buyFood() {
}
/**
* 复写
*/
@Override
protected boolean isHave() {
return true;
}
/**
* 复写
*/
@Override
protected void doSomething() {
System.out.println("朋友打电话来了,要一起出去玩,不吃了");
}
}
测试:
package demo1;
/**
*
* @ClassName: ClientTest
* @Description: 测试
* @author cheng
* @date 2017年8月14日 下午9:45:19
*/
public class ClientTest {
public static void main(String[] args) {
System.out.println("=================张三烧菜===================");
AbstractCooking zhangsan = new ZhangSanCooking();
zhangsan.cooking();
System.out.println("=================李四烧菜===================");
AbstractCooking lisi = new LiSiCooking();
lisi.cooking();
}
}
运行结果:
模版方法模式的优缺点和使用场景
优点
模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
子类实现算法的某些细节,有助于算法的扩展。
缺点
每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
使用场景
各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
面对重要复杂的算法,可以把核心算法设计为模版方法,周边相关细节功能则有各个子类实现。
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
模板方法模式和策略模式的区别
Strategy模式中,为了让Context类能够调用,Strategy接口里声明的方法一般是公有的.Template Method模式则不然,基类中留下的虚方法并不一定要是公有的,只要保证对继承类可见就行.也就是说,Template Method模式允许编写库的人采取更紧的访问限制,而Strategy模式则很难做到相同等级的限制.假如使用者获得了一个Strategy接口的实现类的实例,他并不一定要将这个实例放入”原本应有”的那个Context,而可以随意使用其中的接口方法.Template Method模式可以利用protected的访问权限,牺牲一点面向对象的封装性,给自己的继承类一定的访问特权,来把一些访问限制在”体系内”,从而限制了外部对内的访问.这仍然只是表象,不过我们已经接近问题的本质了.
这带来的区别是什么呢? Strategy模式允许外界使用其接口方法,因而可以将这个接口方法认为是”一整个算法”;而Template Method模式可以限制所留下的虚方法只对其继承类可见,外部使用者不一定能够直接使用这些虚方法,因而可以将这些虚方法认为是”一个算法的一部分”.GoF的设计模式那本书里有这么一句话:”Template methods use inheritance to vary part of an algorithm. Strategies use delegation to vary the entire algorithm.”,说的正是这个问题.回到具体问题上,如果我们要封装的算法适合于提供给用户任意使用,是”一整个算法”,那么用Strategy模式较好;如果要封装的变化是一个算法中的部分(换言之,大算法的步骤是固定的),而且我们不希望用户直接使用这些方法,那么应该使用Template Method模式.就此,问题的”痛处”算是抓住了.