一、概念
定义一个算法的骨架,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方法实现这些抽象方法,从而对剩余的逻辑有不同的实现。意图:在一个方法中实现一个算法,并推迟定义算法中的某些步骤,从而让子类重新定义它们。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。
抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏直接决定了程序是否稳定性。实现类用来实现细节。抽象类中的模版方法正是通过实现类扩展的方法来完成业务逻辑。只要实现类中的扩展方法通过了单元测试,在模版方法正确的前提下,整体功能一般不会出现大的错误。
(1)定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
(2)定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体模板(Concrete Template)角色:
(1)实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
(2)每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法的不同实现,从而使得顶级逻辑的实现各不相同。
三、模式中的方法种类
模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:
① 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现;
② 模版方法(具体方法):由抽象类声明并加以实现,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。子类可以置换掉父类的可变部分,但是子类却不可以改变模板方法所代表的顶级逻辑。另外,一个抽象类可以有任意多个模板方法每一个模板方法都可以调用任意多个具体方法。
③ 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。通常抽象类给出的实现是一个空实现,作为方法的默认实现。
(2)便于维护:对于模版方法模式来说,正是由于他们的主要逻辑相同,才使用了模版方法,假如不使用模版方法,任由这些相同的代码散乱的分布在不同的类中,维护起来是非常不方便的。
(3)比较灵活:因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。
(4)在多个子类拥有相同的方法,并且这些方法逻辑相同时,可以考虑使用模版方法模式。在程序的主框架相同,细节不同的场合下,也比较适合使用这种模式。
定义一个算法的骨架,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方法实现这些抽象方法,从而对剩余的逻辑有不同的实现。意图:在一个方法中实现一个算法,并推迟定义算法中的某些步骤,从而让子类重新定义它们。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。
抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏直接决定了程序是否稳定性。实现类用来实现细节。抽象类中的模版方法正是通过实现类扩展的方法来完成业务逻辑。只要实现类中的扩展方法通过了单元测试,在模版方法正确的前提下,整体功能一般不会出现大的错误。
二、模式结构与参与者
(1)定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
(2)定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体模板(Concrete Template)角色:
(1)实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
(2)每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法的不同实现,从而使得顶级逻辑的实现各不相同。
三、模式中的方法种类
模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:
① 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现;
② 模版方法(具体方法):由抽象类声明并加以实现,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。子类可以置换掉父类的可变部分,但是子类却不可以改变模板方法所代表的顶级逻辑。另外,一个抽象类可以有任意多个模板方法每一个模板方法都可以调用任意多个具体方法。
③ 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。通常抽象类给出的实现是一个空实现,作为方法的默认实现。
四、示例程序
//抽象类,充当模板角色
public abstract class AbstractDisplay {
// 钩子方法
public void open(){
}
// 由子类实现的抽象方法
public abstract void print();
public abstract void close();
// 抽象类实现的方法,final可以保证在子类不会被修改,模板方法
public final void display() {
open();
for (int i = 0; i < 5; i++) {
print();
}
close();
}
}
public class CharDisplay extends AbstractDisplay {
private char ch;
public CharDisplay(char ch) {
this.ch = ch;
}
@Override
public void open() {
System.out.print("<<");
}
@Override
public void print() {
System.out.print(ch + " ");
}
@Override
public void close() {
System.out.println(">>");
}
}
public class StringDisplay extends AbstractDisplay {
private String string; // 应输出的字符串
private int width; // 以byte为单位所求出的字符串的"长度"
public StringDisplay(String string) {
this.string = string;
this.width = string.getBytes().length;
}
public void open() { // 打印头装饰字符串
printLine();
}
public void print() {
System.out.println("|" + string + "|");
}
public void close() {
printLine();
}
public void printLine() {
System.out.print("+");
for (int i = 0; i < width; ++i) {
System.out.print("-"); // 当作线段
}
System.out.println("+");
}
}
public class Main {
public static void main(String[] args) {
// 建立1个有'A'的CharDisplay的对象
AbstractDisplay d1 = new CharDisplay('A');
// 建立1个有"Hello world"的StringDisplay的对象
AbstractDisplay d2 = new StringDisplay("Hello World");
// d1,d2都是AbstractDisplay的子类对象,可以调用继承到的display()方法
d1.display();
d2.display();
}
}
<<A A A A A >>
+-----------+
|Hello World|
|Hello World|
|Hello World|
|Hello World|
|Hello World|
+-----------+
五、模版方法的优点及适用场景
(1)容易扩展:一般来说,抽象类中的模版方法是不易反生改变的部分,而抽象方法是容易反生变化的部分,因此通过增加实现类一般可以很容易实现功能的扩展,符合开闭原则。(2)便于维护:对于模版方法模式来说,正是由于他们的主要逻辑相同,才使用了模版方法,假如不使用模版方法,任由这些相同的代码散乱的分布在不同的类中,维护起来是非常不方便的。
(3)比较灵活:因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。
(4)在多个子类拥有相同的方法,并且这些方法逻辑相同时,可以考虑使用模版方法模式。在程序的主框架相同,细节不同的场合下,也比较适合使用这种模式。