JAVA 设计模式 模板方法模式

定义

模板方法模式 (Template Method)

定义了一个操作中的算法的骨架,而将部分步骤的实现在子类中完成。 模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法模式是所有模式中最为常见的几个模式之一,是基于继承代码复用的基本技术。,没有关联关系。

因此,在模板方法模式的类结构图中,只有继承关系

模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。

代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。

结构

图-模板方法模式结构图

AbstractClass : 抽象类,定义并实现一个模板方法。这个模板方法定义了算法的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类去实现。顶级逻辑也有可能调用一些具体方法。

abstract class AbstractClass {
 public abstract void PrimitiveOperation1();
 public abstract void PrimitiveOperation2();
 
 public void TemplateMethod() {
         PrimitiveOperation1();
         PrimitiveOperation2();
     }
 }

ConcreteClass : 实现实现父类所定义的一个或多个抽象方法。

class ConcreteClassA extends AbstractClass {
     @Override
 public void PrimitiveOperation1() {
         System.out.println("具体A类方法1");
     }
 
     @Override
 public void PrimitiveOperation2() {
         System.out.println("具体A类方法2");
     }
 }
 
 class ConcreteClassB extends AbstractClass {
     @Override
 public void PrimitiveOperation1() {
         System.out.println("具体B类方法1");
     }
 
     @Override
 public void PrimitiveOperation2() {
         System.out.println("具体B类方法2");
     }    
 }

测试代码

public class TemplateMethodPattern {
 public static void main(String[] args) {
         AbstractClass objA = new ConcreteClassA();
         AbstractClass objB = new ConcreteClassB();    
         objA.TemplateMethod();
         objB.TemplateMethod();
     }
 }

要点

模板方法模式中的三类角色 1、具体方法(Concrete Method)

2、抽象方法(Abstract Method)

3、钩子方法(Hook Method)

三类角色的关联

在模板方法模式中,首先父类会定义一个算法的框架,即实现算法所必须的所有方法。

其中,具有共性的代码放在父类的具体方法中。

各个子类特殊性的代码放在子类的具体方法中。但是父类中需要有对应抽象方法声明。

钩子方法可以让子类决定是否对算法的不同点进行挂钩。

总结

使用模板方法模式可以将代码的公共行为提取,以达到复用的目的。

而对于特殊化的行为在子类中实现。父类的模板方法可以控制子类中的具体实现。

子类无需了解整体算法框架,只需实现自己的业务逻辑即可。

实例

模板方法模式应用场景十分广泛。

在《Head First》的模板方法模式章节里列举了一个十分具有代表性的例子。 

现实生活中,茶和咖啡是随处可见的饮料。冲泡一杯茶或冲泡一杯咖啡的过程是怎样的?

我们来整理一下流程。

泡茶: 烧开水 ==> 冲泡茶叶 ==> 倒入杯中 ==> 添加柠檬 泡咖啡: 烧开水 ==> 冲泡咖啡 ==> 倒入杯中 ==> 添加糖和牛奶

由以上处理步骤不难发现,准备这两种饮料的处理过程非常相似。我们可以使用模板类方法去限定制作饮料的算法框架。

其中相同的具有共性的步骤(如烧开水、倒入杯中),直接在抽象类中给出具体实现。

而对于有差异性的步骤,则在各自的具体类中给出实现。

抽象类

abstract class Beverage {
 
 // 模板方法,决定了算法骨架。相当于TemplateMethod()方法
  public void prepareBeverage() {
         boilWater();
         brew();
         pourInCup();
 if (customWantsCondiments())
         {
             addCondiments();
         }
     }
 
 // 共性操作,直接在抽象类中定义
  public void boilWater() {
         System.out.println("烧开水");
     }
 
 // 共性操作,直接在抽象类中定义
  public void pourInCup() {
         System.out.println("倒入杯中");
     }
 
 // 钩子方法,决定某些算法步骤是否挂钩在算法中
  public boolean customWantsCondiments() {
 return true;
     }
 
 // 特殊操作,在子类中具体实现
  public abstract void brew();
 
 // 特殊操作,在子类中具体实现
  public abstract void addCondiments();
 
 }

具体类

class Tea extends Beverage {
 
     @Override
 public void brew() {
         System.out.println("冲泡茶叶");
     }
 
     @Override
 public void addCondiments() {
         System.out.println("添加柠檬");
     }
 
 }
 
 class Coffee extends Beverage {
 
     @Override
 public void brew() {
         System.out.println("冲泡咖啡豆");
     }
 
     @Override
 public void addCondiments() {
         System.out.println("添加糖和牛奶");
     }
 
 }

测试代码

public static void main(String[] args) {
 
     System.out.println("============= 准备茶 =============");
     Beverage tea = new Tea();
     tea.prepareBeverage();
 
     System.out.println("============= 准备咖啡 =============");
     Beverage coffee = new Coffee();
     coffee.prepareBeverage();
 
 }

运行结果

============= 准备茶 =============
 烧开水
 冲泡茶叶
 倒入杯中
 添加柠檬
 ============= 准备咖啡 =============
 烧开水
 冲泡咖啡豆
 倒入杯中
 添加糖和牛奶

推荐

本文属于 JAVA设计模式系列

参考资料

《大话设计模式》 《HeadFirst设计模式》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏me的随笔

模板方法模式实践

在实际编程中,会经常遇到多个类中的某些方法实现逻辑类似的情况,这时我们可以将这些类中的相同部分抽象到父类中,对于有差异的地方,子类根据自身的实际需求来各自实现。

692
来自专栏WindCoder

Java设计模式学习笔记—建造者模式

文章最后“Java设计模式笔记示例代码整合”为本系列代码整合,所有代码均为个人手打并运行测试,不定期更新。本节内容位于其Builder包(package)中。

612
来自专栏xingoo, 一个梦想做发明家的程序员

希尔排序

希尔排序的时间复杂度,最好的情况下仍然是正序时,可达到O(n),平均复杂度为O(nlogn)。 算法思想: 采用跳跃式处理数组,使得数组粗粒度的实现基本有序。...

1955
来自专栏Golang语言社区

Go 语言Map(集合)

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。 Map 是一种集合,所以我们可以像迭代...

3427
来自专栏向治洪

android EventBus详解(二)

上一节讲了EventBus的使用方法和实现的原理,下面说一下EventBus的Poster只对粘滞事件和invokeSubscriber()方法是怎么发送的。 ...

19310
来自专栏写代码的海盗

脱掉Golang的第一层衣裳 golang入坑系列

海鳖曾欺井内蛙,大鹏张翅绕天涯。强中更有强中手,莫向人前满自夸。 各位看官,现在开始脱衣裳。你不用脱,自个衣裳要穿好了,别脱下来。我们是来学Golang的,不...

2493
来自专栏GreenLeaves

C# 委托进阶

本文参考自:https://wenku.baidu.com/view/41ab91d3c1c708a1284a44d7.html?qq-pf-to=pcqq.c...

1826
来自专栏大内老A

谈谈WCF中的Data Contract(2):WCF Data Contract对Generic的支持

通过第一部分的介绍,我们可以体会到,WCF 的Data Contract在CLR Type和Neutral Contract之间搭建了一座桥梁,弥合了.NET世...

1816
来自专栏三好码农的三亩自留地

RxJava2 源码解读之 ConcatMap

之前分析了FlatMap发射数据无序的原因,但是没有实际用代码验证过,这里我们在分析ConcatMap源码之前,我们先运行测试代码,有个直观的感受。

803
来自专栏Golang语言社区

Golang语言--映射

Go编程提供另一个重要的数据类型是映射,唯一映射一个键到一个值。一个键要使用在以后检索值的对象。给定的键和值,可以在一个Map对象存储的值。值存储后,您可以使用...

3315

扫码关注云+社区