模式定义
所谓模板方法模式就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式是基于继承的代码复用技术的。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。
其实所谓模板就是一个方法,这个方法将算法的实现定义成了一组步骤,其中任何步骤都是可以抽象的,交由子类来负责实现。这样就可以保证算法的结构保持不变,同时由子类提供部分实现。
模板是一个方法,那么他与普通的方法存在什么不同呢?模板方法是定义在抽象类中,把基本操作方法组合在一起形成一个总算法或者一组步骤的方法。而普通的方法是实现各个步骤的方法,我们可以认为普通方法是模板方法的一个组成部分。
模式结构
模式实现
加入一个场景,”小时候,数学老师的随堂测试,都是在黑板上抄题,要我们抄题目,然后在做答案,那个时候假如你已经近视了,刚好那天你没带眼镜,所以有时候会抄错题目,比如把3看成8,7看成1,那就意味着你做的再好,也不会正确。惨了。。。“
这其实就是一个典型的模板方法模式
AbstractClass
package com.bjsxt.templatemethod.over;
abstract class TestPaper {
public abstract String Answer1();
public abstract String Answer2();
public abstract String Answer3();
public void testQuestion1(){
System.out.println("1 + 0 = A 1 B 2 C 3" + "答案:" + Answer1() );
}
public void testQuestion2(){
System.out.println("1 + 1 = A 1 B 2 C 3" + "答案:" + Answer2() );
}
public void testQuestion3(){
System.out.println("1 + 2 = A 1 B 2 C 3" + "答案:" + Answer3() );
}
public void result(){
testQuestion1();
testQuestion2();
testQuestion3();
}
}
ConcreteClass
package com.bjsxt.templatemethod.over;
public class TestPaperA extends TestPaper {
public String Answer1() {
return "A";
}
public String Answer2() {
return "B";
}
public String Answer3() {
return "C";
}
}
package com.bjsxt.templatemethod.over;
public class TestPaperB extends TestPaper {
public String Answer1() {
return "B";
}
public String Answer2() {
return "A";
}
public String Answer3() {
return "C";
}
}
package com.bjsxt.templatemethod.over;
public class Client {
public static void main(String[] args) {
TestPaper testPaperA = new TestPaperA();
TestPaper testPaperB = new TestPaperB();
System.out.println("the result of testPaperA");
testPaperA.result();
System.out.println("the result of testPaperB");
testPaperB.result();
}
}
从上面的运行结果可以看出,我们的模板方法模式表现的非常良好,但是我们似乎忽略了一些东西?如果有一些学生选择不做一些题呢,但是每次我们必须填写答案,那末怎么解决这个问题呢?
遇到这个问题我们可以使用钩子。所谓钩子就是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在可以使子类能够对算法的不同点进行挂钩,即让子类能够对模板方法中某些即将发生变化的步骤做出相应的反应。当然要不要挂钩,由子类决定。
对于上述要求我们可以做出如下改进
AbstractClass
package com.bjsxt.templatemethod;
abstract class TestPaper {
public abstract String Answer1();
public abstract String Answer2();
public abstract String Answer3();
public void testQuestion1(){
System.out.println("1 + 0 = A 1 B 2 C 3" + "答案:" + Answer1() );
}
public void testQuestion2(){
System.out.println("1 + 1 = A 1 B 2 C 3" + "答案:" + Answer2() );
}
public void testQuestion3(){
System.out.println("1 + 2 = A 1 B 2 C 3" + "答案:" + Answer3() );
}
public void result(){
testQuestion1();
testQuestion2();
if(wantSolve()){
testQuestion3();
}
}
public boolean wantSolve(){
return true;
}
}
ConcreteClass
package com.bjsxt.templatemethod;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
public class TestPaperA extends TestPaper {
public String Answer1() {
return "A";
}
public String Answer2() {
return "B";
}
public String Answer3() {
return "C";
}
public boolean wantSolve() {
if("y".equals(getStudentInput().toLowerCase()))
return true;
return false;
}
public String getStudentInput(){
String result = null;
System.out.println("would you want to solve the question? please input y or n");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
result = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if(result == null) result = "n";
return result;
}
}
package com.bjsxt.templatemethod;
public class TestPaperB extends TestPaper {
public String Answer1() {
return "B";
}
public String Answer2() {
return "A";
}
public String Answer3() {
return "C";
}
}
Client
package com.bjsxt.templatemethod;
public class Client {
public static void main(String[] args) {
TestPaper testPaperA = new TestPaperA();
TestPaper testPaperB = new TestPaperB();
System.out.println("the result of testPaperA");
testPaperA.result();
System.out.println("the result of testPaperB");
testPaperB.result();
}
}
模式优缺点
优点
1、模板方法模式在定义了一组算法,将具体的实现交由子类负责。
2、模板方法模式是一种代码复用的基本技术。
3、模板方法模式导致一种反向的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合“开闭原则”。
缺点
每一个不同的实现都需要一个子类来实现,导致类的个数增加,是的系统更加庞大。
使用场景
个子类中公共的行为被提取到超类中,避免了代码的重复。