模版模式,作为一种行为型模式,通过在抽象类或接口中定义一个操作中的算法骨架,而将一些步骤具体执行延迟到子类中实现,从而使得父类的方法执行可以获得不一样的结果。从而达到了代码复用、扩展性好、灵活度高的设计目的。
使用时机
模版模式使用时机,主要是相同、相似方法使用较多的情况。采用模版模式,可以将这些相似的方法提取出来,制定出一个相对普适的模版,从而减少代码的重复书写,提高代码的复用率。抽象化的使用场景及实现逻辑如下图所示。
基本定义
在了解如何写模版模式之前,我们有必要来了解一些有关模版模式的关键名词及术语。
基本方法:
基本方法又可以划分为如下三类:
1、具体方法(Concrete Method)
具体方法指的是由父类/接口负责实现的方法,子类不可以对该方法进行更改。
2、抽象方法(Abstract Method)
抽象方法则指的是不由父类/接口实现具体步骤,而是将其具体操作步骤推迟到子类实现,从而对不同情况实现不同的操作。
3、钩子方法(Hook Method)
一个钩子方法由抽象类声明并实现,而子类会加以扩展。**它是子类可以选择性实现或不实现的方法**,通常抽象类给出是一个空实现,作为方法的默认实现。
这样的默认实现,被称为**默认钩子方法**。这种空钩子方法也叫做“Do Nothing Hook”。默认钩子方法在缺省适配模式也有相应的使用。缺省适配模式讲的是一个类为接口提供一个默认的空实现,从而使得子类不必给出所有方法的实现,因为通常一个具体类并不需要所有的方法。这个思想正好同默认钩子方法不谋而合。
钩子方法常见的用途为,将两个存在不同调用关系的pipeLine流程,通过钩子方法联系到同一个模版中,从而屏蔽不同内容的差异性。但是,需要注意的一点是,**钩子方法的名字应当以do开始,这是熟悉设计模式的Java开发人员的标准做法。**如doScan、doGet等。
模版方法:
模版方法是模版模式的核心点,其是定义在抽象类中的,是把基本操作方法组合在一起形成总的算法或行为的方法。一个抽象类可以有**任意多个模板方法**,而不限于一个。每一个模板方法都可以调用任意多个具体方法。原则上,**子类不可以、也不应该对模版方法进行覆盖或者重写**。
上述定义中各类方法的对应模块大致如下图所示:
代码实践
模版模式的实现,在java中有两种方式,一种是基于抽象类的实现方式,另外一种则是基于接口的实现方式。
这里我们以抽象类的实现方法为例子介绍相应的模版模式的实现。
public abstract class Template {
public void concreteMethod() {
System.out.println("concreteMethod:");
}
public abstract void abstractMethod();
public void hookMethod(){
System.out.println("hookMethod:");
System.out.println("实现默认hookMethod!");
}
public final void execute() {
concreteMethod();
abstractMethod();
hookMethod();
}
}
首先,定义出我们的模版接口,其中包含三个方法concreteMethod、abstractMethod、hookMethod。就分别对应于我们上述提到的具体方法、抽象方法及钩子方法。
然后在execute()方法内,定义好三个方法的基本执行方法,同时采用final修饰符,使得子类无法修改execute方法中的执行顺序。
然后在我们的子类HerFuction中,首先必须要对抽象方法进行相应的实现。这里我们简单的输出类名。
而在钩子方法hookMethod中,我们则对原方法进行增强,多输出一句话:“我还要执行自己的hookMethod方法!”。
public class HerFunction implements Template {
@Override
public void abstractMethod() {
System.out.println("HerFunciton !");
}
@Override
public void hookMethod() {
Template.super.hookMethod();
System.out.println("我还要执行自己的hookMethod方法!");
}
}
类似地,在第二个子类MyFunction中,我们也需要实现相应的抽象类方法。但对于钩子方法则采用默认的抽象类中的方法实现即可。
public class MyFunction extends Template {
@Override
public void abstractMethod() {
System.out.println("My Function!");
}
}
最后,在启动方法中分别创建MyFunction、HerFunction对应的对象,并调用父类的execute方法即可。
public static void main(String[] args) {
Template myFunction = new MyFunction();
myFunction.execute();
System.out.println("================我是分割线=========================");
Template herFunction = new HerFunction();
herFunction.execute();
}
最后得到的结果如下:
可以看到,子类对concreteMethod方法都成功实现了复用,而对于abstractMethod则根据不同子类实现了不同的逻辑,体现了差异性。同时钩子方法的默认实现及子类实现,也体现了模版的灵活性。
优缺点分析
优点:
1、利用模板模式将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
2、将相同业务含义的处理代码放置到不同的子类中,通过对子类的扩展增加新的行为,从而提高代码的扩展性。
3、把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
缺点:
1、模版模式的实现依赖于子类的构建,因此类的数量会存在明显的增加,增加了类的复杂度。
2、采用抽象类情况下,继承关系自身存在一定缺点,如果父类添加新的抽象方法,所有子类都要对该抽象方法进行实现。JAVA语言可以采用接口+default关键字的方式,一定程度上避免这个修改。(但是带来的副作用是不能采用final对方法进行限制)