一、模板模式式定义

  • ①:模板模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板模式使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤。
  • ②;这种类型的设计模式属于行为型模式。
  • ③:为防止恶意操作,一般模板方法都加上final关键词。

二、模板方法模式的基本结构

模板方法模式 - 图1

AbstractClass是一个抽象类,其实就是一个抽象模板,定义并实现了一个模板方法。这个模板方法一般是一个具体的实现,他给出了一些逻辑的骨架,而逻辑的组成在相应的抽象类中,推迟到了子类实现:

  1. public abstract class AbstractClass {
  2. //一些抽象行为,可以理解为重复不变的方法,提取到抽象类
  3. public abstract void primitiveOperation1();
  4. public abstract void primitiveOperation2();
  5. //模板方法,给出了具体逻辑的骨架,而逻辑的组成是一些相应的抽象操作,他们都推迟到子类实现
  6. public final void templateMothed() {
  7. primitiveOperation1();
  8. primitiveOperation2();
  9. }
  10. }

ConcreteClass实现父类所定义的一个或多个抽象方法。每一个AbstractClass都可以有一个或者多个ConcreteClass与之对应,而每一个ConcreteClass都可以给出这些抽象方法(也就是骨架的组成步骤)的不同实现,从而得到的实现都不同:
具体实现ConcreteClassA

  1. public class ConcreteClassA extends AbstractClass {
  2. @Override
  3. public void primitiveOperation1() {
  4. System.out.println("子类A的操作1");
  5. }
  6. @Override
  7. public void primitiveOperation2() {
  8. System.out.println("子类A的操作2");
  9. }
  10. }

具体实现ConcreteClassB

  1. public class ConcreteClassB extends AbstractClass {
  2. @Override
  3. public void primitiveOperation1() {
  4. System.out.println("子类B的操作1");
  5. }
  6. @Override
  7. public void primitiveOperation2() {
  8. System.out.println("子类B的操作2");
  9. }
  10. }

上面定义了两个具体的实现,更多的实现其实都是一致的,这里就不多多说了。下面看下客户端代码:

  1. public class Client {
  2. public static void main(String[] args) {
  3. AbstractClass obj_1 = new ConcreteClassA();
  4. obj_1.templateMothed();
  5. AbstractClass obj_2 = new ConcreteClassB();
  6. obj_2.templateMothed();
  7. }
  8. }

输出内容如下:

  1. 子类A的操作1
  2. 子类A的操作2
  3. 子类B的操作1
  4. 子类B的操作2

三、没有使用设计模式

笔者就以抄试卷模式为名来阐述重复不变带来的不便,下面会对该模式进行改进:
学生甲抄的试卷:

  1. public class TestPaperA {
  2. //试卷第一题
  3. public void testQuestion1() {
  4. System.out.println("小龙女是杨过的什么亲戚?() A.小姨妈 B.大姨妈 C.姑妈 D.舅妈");
  5. System.out.println("答案:C");
  6. }
  7. //试卷第二题
  8. public void testQuestion2() {
  9. System.out.println("全真教的首任掌门是谁?A.周伯通 B.欧阳锋 C.王重阳 D.西门吹牛");
  10. System.out.println("答案:C");
  11. }
  12. //试卷第三题
  13. public void testQuestion3() {
  14. System.out.println("《天龙八部》中被封为南院大王的大侠是谁?A.段誉 B.乔峰 C.慕容复 D.段智兴");
  15. System.out.println("答案:B");
  16. }
  17. }

学生乙抄的试卷:

  1. public class TestPaperB {
  2. //试卷第一题
  3. public void testQuestion1() {
  4. System.out.println("小龙女是杨过的什么亲戚?() A.小姨妈 B.大姨妈 C.姑妈 D.舅妈");
  5. System.out.println("答案:A");
  6. }
  7. //试卷第二题
  8. public void testQuestion2() {
  9. System.out.println("全真教的首任掌门是谁?A.周伯通 B.欧阳锋 C.王重阳 D.西门吹牛");
  10. System.out.println("答案:C");
  11. }
  12. //试卷第三题
  13. public void testQuestion3() {
  14. System.out.println("《天龙八部》中被封为南院大王的大侠是谁?A.段誉 B.乔峰 C.慕容复 D.段智兴");
  15. System.out.println("答案:D");
  16. }
  17. }

客户端代码:

  1. public class Client {
  2. public static void main(String[] args) {
  3. System.out.println("========== 学生甲的试卷 ==========");
  4. TestPaperA stuA = new TestPaperA();
  5. stuA.testQuestion1();
  6. stuA.testQuestion2();
  7. stuA.testQuestion3();
  8. System.out.println("========== 学生乙的试卷 ========== ");
  9. TestPaperB stuB = new TestPaperB();
  10. stuB.testQuestion1();
  11. stuB.testQuestion2();
  12. stuB.testQuestion3();
  13. }
  14. }

输出内容如下:

  1. ========== 学生甲的试卷 ==========
  2. 小龙女是杨过的什么亲戚?() A.小姨妈 B.大姨妈 C.姑妈 D.舅妈
  3. 答案:C
  4. 全真教的首任掌门是谁?A.周伯通 B.欧阳锋 C.王重阳 D.西门吹牛
  5. 答案:C
  6. 《天龙八部》中被封为南院大王的大侠是谁?A.段誉 B.乔峰 C.慕容复 D.段智兴
  7. 答案:B
  8. ========== 学生乙的试卷 ==========
  9. 小龙女是杨过的什么亲戚?() A.小姨妈 B.大姨妈 C.姑妈 D.舅妈
  10. 答案:A
  11. 全真教的首任掌门是谁?A.周伯通 B.欧阳锋 C.王重阳 D.西门吹牛
  12. 答案:C
  13. 《天龙八部》中被封为南院大王的大侠是谁?A.段誉 B.乔峰 C.慕容复 D.段智兴
  14. 答案:D

很容易发现上面两个学生抄的试卷有很多重复的地方,比如试卷的题目,输出答案的方法。这些都在每个学生试卷类中混合在一起了,既不利于维护,也不利于浏览,下面看一下模板方法模式是怎么改进的。

四、使用模板模式进行改造

将每个学生试卷的重复部分提取出来,题目、作答等等。首先改造试卷类,将该类改为抽象类。在该类中我添加了三个抽象的方法用于子类实现。
学生都是要作答的,但是答案不一样,所以可以将作答的过程作为重复不变的方法提取出来。

创建TestPaper试卷模板抽象类:

为防止恶意操作,一般模板方法都加上final关键词

  1. public abstract class TestPaper {
  2. //试卷第一题
  3. public void testQuestion1() {
  4. System.out.println("小龙女是杨过的什么亲戚?() A.小姨妈 B.大姨妈 C.姑妈 D.舅妈");
  5. System.out.println("答案:" + answer1());
  6. }
  7. //试卷第二题
  8. public void testQuestion2() {
  9. System.out.println("全真教的首任掌门是谁?A.周伯通 B.欧阳锋 C.王重阳 D.西门吹牛");
  10. System.out.println("答案:" + answer2());
  11. }
  12. //试卷第三题
  13. public void testQuestion3() {
  14. System.out.println("《天龙八部》中被封为南院大王的大侠是谁?A.段誉 B.乔峰 C.慕容复 D.段智兴");
  15. System.out.println("答案:" + answer3());
  16. }
  17. //这三个钩子方法是给每个子类去实现,并返回答案的
  18. public abstract String answer1();
  19. public abstract String answer2();
  20. public abstract String answer3();
  21. //模板方法,考试的过程,定义基本的考试过程,子类回调
  22. public final void exam() {
  23. testQuestion1();
  24. testQuestion2();
  25. testQuestion3();
  26. }
  27. }

创建TestPaper抽象类的TestPaperA具体实现类:

  1. public class TestPaperA extends TestPaper {
  2. @Override
  3. public String answer1() {
  4. return "A";
  5. }
  6. @Override
  7. public String answer2() {
  8. return "B";
  9. }
  10. @Override
  11. public String answer3() {
  12. return "D";
  13. }
  14. }

创建TestPaper抽象类的TestPaperB具体实现类:

  1. public class TestPaperB extends TestPaper {
  2. @Override
  3. public String answer1() {
  4. return "C";
  5. }
  6. @Override
  7. public String answer2() {
  8. return "A";
  9. }
  10. @Override
  11. public String answer3() {
  12. return "A";
  13. }
  14. }

客户端代码:

  1. public class Client {
  2. public static void main(String[] args) {
  3. TestPaper testPaper = new TestPaperA();
  4. testPaper.exam();
  5. }
  6. }

输出内容如下:

  1. 小龙女是杨过的什么亲戚?() A.小姨妈 B.大姨妈 C.姑妈 D.舅妈
  2. 答案:A
  3. 全真教的首任掌门是谁?A.周伯通 B.欧阳锋 C.王重阳 D.西门吹牛
  4. 答案:B
  5. 《天龙八部》中被封为南院大王的大侠是谁?A.段誉 B.乔峰 C.慕容复 D.段智兴
  6. 答案:D

五、总结

模板方法模式就是为了将重复不变的代码提取到一个抽象类中。当我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模板方法模式来处理。