举个栗子
问题描述
小时候数学老师的随堂测验,都是在黑板上抄题目,然后再作答案。写一个抄题目的程序。
简单实现
学生甲抄的试卷
/**
* 学生甲抄的试卷
* Created by callmeDevil on 2019/7/14.
*/
public class TestPaperA {
// 试题1
public void testQuestion1(){
System.out.println("路飞在顶上战争之后,修炼了多久? a.3天 b.2年 c.2天 d.3年");
System.out.println("答案:b");
}
// 试题2
public void testQuestion2(){
System.out.println("鸣人是第几代火影? a.六代 b.七代 c.八代 d.九代");
System.out.println("答案:b");
}
}
学生乙抄的试卷
/**
* 学生乙抄的试卷
* Created by callmeDevil on 2019/7/14.
*/
public class TestPaperB {
// 试题1
public void testQuestion1(){
System.out.println("路飞在顶上战争之后,修炼了多久? a.3天 b.2年 c.2天 d.3年");
System.out.println("答案:a");
}
// 试题2
public void testQuestion2(){
System.out.println("鸣人是第几代火影? a.六代 b.七代 c.八代 d.九代");
System.out.println("答案:a");
}
}
测试
public class Test {
public static void main(String[] args) {
System.out.println("学生甲抄的试卷:");
TestPaperA studentA = new TestPaperA();
studentA.testQuestion1();
studentA.testQuestion2();
System.out.println("学生乙抄的试卷:");
TestPaperB studentB = new TestPaperB();
studentB.testQuestion1();
studentB.testQuestion2();
}
}
测试结果
学生甲抄的试卷:
路飞在顶上战争之后,修炼了多久? a.3天 b.2年 c.2天 d.3年
答案:b
鸣人是第几代火影? a.六代 b.七代 c.八代 d.九代
答案:b
学生乙抄的试卷:
路飞在顶上战争之后,修炼了多久? a.3天 b.2年 c.2天 d.3年
答案:a
鸣人是第几代火影? a.六代 b.七代 c.八代 d.九代
答案:a
存在问题
两个学生抄试卷都非常类似,除了答案不同,没什么不一样,这样又容易错,又难以维护。因此老师出一份试卷,打印多份,让学生填写答案就可以了。应该把试题和答案分离,抽出一个父类,让两个子类继承它,公共的试题代码写到父类当中就行了。
提炼代码
试题父类
/**
* 试题父类-动漫考题
* Created by callmeDevil on 2019/7/14.
*/
public class TestPaper {
// 试题1
public void testQuestion1(){
System.out.println("路飞在顶上战争之后,修炼了多久? a.3天 b.2年 c.2天 d.3年");
}
// 试题2
public void testQuestion2(){
System.out.println("鸣人是第几代火影? a.六代 b.七代 c.八代 d.九代");
}
}
学生甲抄的试卷-版本2
/**
* 学生甲抄的试卷-版本2
* Created by callmeDevil on 2019/7/14.
*/
public class TestPaperA2 extends TestPaper{
@Override
public void testQuestion1() {
super.testQuestion1();
System.out.println("答案:b");
}
@Override
public void testQuestion2() {
super.testQuestion2();
System.out.println("答案:b");
}
}
学生乙抄的试卷-版本2
/**
* 学生乙抄的试卷-版本2
* Created by callmeDevil on 2019/7/14.
*/
public class TestPaperB2 extends TestPaper{
@Override
public void testQuestion1(){
super.testQuestion1();
System.out.println("答案:a");
}
@Override
public void testQuestion2(){
super.testQuestion2();
System.out.println("答案:a");
}
}
客户端测试代码与测试结果此处省略,同上述简单实现。
存在的问题
- 既然用了继承,并且肯定这个继承有意义,就应该要成为子类的模板,所有重复的代码都应该要上升到父类去,而不是让每个子类都去重复
- 当我们要完成在某一个细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模板方法模式来处理
- 在上面提炼的代码中,每个学生只是输出的答案不同,其他全部都是一样的,应该在此处改成一个虚方法,让每个子类去重写即可
模板方法模式
定义
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤。
UML图
代码实现
模板方法父类-动漫考题
/**
* 模板方法父类-动漫考题
* Created by callmeDevil on 2019/7/14.
*/
public abstract class TestPaper {
// 试题1
public void testQuestion1(){
System.out.println("路飞在顶上战争之后,修炼了多久? a.3天 b.2年 c.2天 d.3年");
System.out.println("答案:" + answer1());//改成一个虚方法,下同
}
// 试题2
public void testQuestion2(){
System.out.println("鸣人是第几代火影? a.六代 b.七代 c.八代 d.九代");
System.out.println("答案:" + answer2());
}
// 此方法的目的就是给继承的子类重写,因为这里每个人的答案都是不同的
public abstract String answer1();
public abstract String answer2();
}
学生甲抄的试卷-模板方法实现
/**
* 学生甲抄的试卷-模板方法实现
* Created by callmeDevil on 2019/7/14.
*/
public class TestPaperA3 extends TestPaper{
@Override
public String answer1() {
return "b";
}
@Override
public String answer2() {
return "b";
}
}
学生乙抄的试卷-模板方法实现
/**
* 学生乙抄的试卷-模板方法实现
* Created by callmeDevil on 2019/7/14.
*/
public class TestPaperB3 extends TestPaper{
@Override
public String answer1() {
return "a";
}
@Override
public String answer2() {
return "a";
}
}
模板方法测试
/**
* 模板方法测试
* Created by callmeDevil on 2019/7/14.
*/
public class Test3 {
public static void main(String[] args) {
System.out.println("学生甲抄的试卷:");
// 将子类变量的声明改成父类,利用多态性实现了代码的复用
TestPaper studentA = new TestPaperA3();
studentA.testQuestion1();
studentA.testQuestion2();
System.out.println("学生乙抄的试卷:");
TestPaper studentB = new TestPaperB3();
studentB.testQuestion1();
studentB.testQuestion2();
}
}
测试结果是一致的,此处不再贴出。
总结
- 模板方法模式是通过把不变的行为搬移到父类,去除子类中重复的代码来体现它的优势
- 模板方法模式就是提供了一个很好的代码复用平台
- 当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。我们通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠。