介绍
什么是模板?
模板的原意是指,通过使用既定的工具、模具来不断的生产相似的产品。如,有镂空文字的塑料模板,我们只需要用笔在模板的镂空文字处临摹,便可写出整齐的文字。不管使用什么样的笔,签字笔,彩笔、铅笔,文字的形状都会与镂空处的形状一致。
什么是Template Method 模式?
顾名思义,其实就是带有模板功能的模式,组成模板的方法被定义在父类中。由于这些方法是抽象方法,不能知道他们是怎么实现的,所以只是查看父类的代码不能知道这些方法最终会做怎样的处理,唯一能知道的就是父类怎么调用这些方法的(父类自身实现的方法)。
而实现父类所定义的抽象方法的则是子类。子类实现了抽象方法就决定了具体的处理,不同的子类实现不同的具体处理,当父类的模板方法被调用时程序的行为也会不同。不管子类的具体实现如何,处理的流程都会按照父类所定义的方式去执行(即父类自身实现的方法定义了怎么去使用抽象方法,子类实现抽象方法的具体实现不同,则结果就会不同)
像这样,父类中定义具体处理流程的框架,子类中实现具体处理行为的模式就成为Template Method模式。
类图:
示例程序
我们现在做一个小的程序来展现我们了解的模板方法模式。这个实例程序是将字符和字符串打印5次的程序
示例程序类图:
AbstractDisplay 类是一个抽象类,分别由一下4个方法,其中open、print、close是抽象方法,具体的实现将交给AbstractDisplay 的子类,display方法是将字符串按照规则打印出来。
规则如下:
调用open方法。
循环调用5次print方法
调用close方法
public abstract class AbstractDisplay {
public abstract void close();
public abstract void open();
public abstract void print();
public void display() {
open();
for (int i = 0; i<5; i++){
print();
}
close();
};
}
CharDisplay 类继承于AbstractDisplay 类,并且对AbstractDisplay 类所有的抽象方法做出了具体的实现。
方法名 | 处理 |
---|---|
open | 显示字符串“<<” |
close | 显示字符串“>>” |
显示构造方法传入的字符 |
当我们调用display方法时,假设我们向CharDisplay类构造函数中传入了一个 ‘H’ 的字符,那么显示的字符串应是:
<<HHHHH>>
public class CharDisplay extends AbstractDisplay {
private char ch;
CharDisplay(char ch){
this.ch = ch;
}
public void close() {
System.out.println(">>");
}
public void open() {
System.out.print("<<");
}
public void print() {
System.out.print(ch);
}
}
StringDisplay 类同样继承于AbstractDisplay,并针对AbstractDisplay类抽象方法做出了不同于CharDisplay 类的具体实现。StringDisplay在创建时需要向构造方法传入一个字符串,并且将这个字符串的字节长度及字符串分别赋予StringDisplay类的两个成员变量,printLine方法是打印上下两条边框的一个方法,供close、open方法进行调用。最终的结果是希望得到一个将字符串包裹的一个边框。
假设我们向StringDisplay 类传入了一个“Hello World。”最终的结果会如此:
+------------+
|Hello World.|
|Hello World.|
|Hello World.|
|Hello World.|
|Hello World.|
+------------+
方法名 | 处理 |
---|---|
open | 显示字符串“±-----------+” |
close | 显示字符串“±-----------+” |
显示构造方法,并用“ |
public class StringDisplay extends AbstractDisplay {
private String str; //需要显示的字符串
private int length; //记录传入的字符串的字节长度
StringDisplay(String str){
this.str = str;
this.length = str.getBytes().length;
}
public void close() {
printLine();
}
public void open() {
printLine();
}
public void print() {
System.out.println("|" + str + "|"); //将字符串打印出来
}
private void printLine(){
System.out.print("+"); //显示边框的角
for (int i= 0; i<length; i++){
System.out.print("-"); //显示的边框
}
System.out.println("+"); //显示边框的角
}
}
模板方法模式的优点:
1、使处理的逻辑通用化,将具体的处理抽象交由子类去实现,具体的使用流程则有父类实现。一套接口,多种实现。
2、使检查和维护变得简单方便,因为具体的使用是由父类进行的,当我们在模板方法中发现bug时,只需要修模板方法中的代码就可解决问题
3、父类和子类拥有一致性,使用父类的类型变量保存子类的示例,无论父类变量中保存的是哪个子类的实例程序都可以正常的运行
模板方法中出现的角色:
1、抽象类(AbstractClass):AbstractClass角色不仅负责实现模板方法,还负责声明在模板方法中所使用到的抽象方法。这些抽象方法由子类(ConcreteClass)角色负责实现。
2、具体类(ConcreteClass):该角色负责实现AbstractClass类中定义的抽象方法,这里实现的方法将会在AbstractClass角色的模板方法中被调用
拓展与延伸:
以类的层次去理解时,通常是站在子类的角度去思考。即:很容易想到以下的几点:
- 子类中可以使用父类定义的方法
- 子类可以添加新的方法实现新的功能
- 子类可以重写父类的方法改变程序的行为
而站在父类的角度去思考,在父类中,我们声明了抽象方法且将该方法交由子类去实现。换而言之,就程序而言,声明抽象方法是希望达到以下的目的:
- 期待子类去实现抽象方法
- 要求子类去实现抽象方法
即:子类具有实现父类中所声明的抽象方法的责任。而这种责任被称为“子类责任”。
父子类的协作:在模板方法(Template Method)模式中,父类和子类是紧密联系的,他们共同支撑起了整个程序。因此在子类实现抽象方法时,必须要理解抽象方法被调用的时机。在看不到父类源代码的情况下想要编写出子类是非常困难的。
虽然**将更多的方法的实现放在父类能有效的减少子类的负担,但同样也降低了程序的灵活性。反之,如果父类中实现的方法过少,也会让子类变得臃肿不堪。**所以,“如何划分处理级别,哪些有子类实现,哪些由父类实现”并没有具体的定式,都需要有程序设计的开发人员根据具体情况决定