模板方法模式(Template Method Pattern)
基本介绍
模板方法模式是编程中经常用得到模式。它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。
核心
处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用模板方法模式,将这个节点的代码实现转移给子类完成。即:处理步骤父类中定义好,具体实现延迟到子类中定义。
什么时候用到模板方法模式?
实现一个算法时,整体步骤很固定。但是,某些部分易变。易变部分可以抽象成出来,供子类实现。
可以使用模板方法模式的例子
- 银行办理业务
- 吃东西
模板方法模式的UML类图
角色分析
- AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法 operationr2,3,4
- ConcreteClass 实现抽象方法 operationr2,3,4, 以完成算法中特点子类的步骤
开发中常见的场景
非常频繁。各个框架、类库中都有他的影子。比如常见的有:
- 数据库访问的封装
- Junit单元测试
- servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- spring中JDBCTemplate、 HibernateTemplate等。
实例分析
编写制作豆浆的程序,说明如下:
制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎。
通过添加不同的配料,可以制作出不同口味的豆浆,选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆。。。),但是不同的豆浆需要添加的配料确是不同的。
模板方法模式比较简单,就不以传统方式分析问题,从而引出模板方法模式了。
模板方法模式实现豆浆制作
思路
将添加配料的方法延伸到子类中具体去实现
UML类图
代码实现
1.抽象类,表示豆浆
添加配料的方法延伸到子类去实现
public abstract class SoyaMilk {
//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
final void make() {
select();
addCondiments();
soak();
beat();
}
//选材料
void select() {
System.out.println("第一步:选择好的新鲜黄豆 ");
}
//添加不同的配料, 抽象方法, 子类具体实现
abstract void addCondiments();
//浸泡
void soak() {
System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
}
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎 ");
}
}
2.子类实现抽象类的抽象方法
红豆豆浆
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的红豆 ");
}
}
花生豆浆
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println(" 加入上好的花生 ");
}
}
3.客户端调用
public class Client {
public static void main(String[] args) {
System.out.println("----制作红豆豆浆----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作花生豆浆----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
}
}
结果
----制作红豆豆浆----
第一步:选择好的新鲜黄豆
加入上好的红豆
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
----制作花生豆浆----
第一步:选择好的新鲜黄豆
加入上好的花生
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
模板方法模式的钩子方法(改进)
分析问题
上面的实现存在一定的问题,比如现在我需要制作纯豆浆,不需要实现父类的定义的抽象方法。所以这里我改进下,使用钩子方法
钩子方法简介
- 子类不调用父类,而通过父类调用子类。这些调用步骤已经在父类写好了,完全由父类来控制整个过程。即定义在父类的抽象方法像个钩子一样,执行时,挂载哪个子类就执行哪个子类的方法。
- 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
改进后代码实现
1.抽象类中添加钩子方法,决定是否要添加配料
public abstract class SoyaMilk {
//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
final void make() {
select();
if(customerWantCondiments()) {
addCondiments();
}
soak();
beat();
}
//选材料
void select() {
System.out.println("第一步:选择好的新鲜黄豆 ");
}
//添加不同的配料, 抽象方法, 子类具体实现
abstract void addCondiments();
//浸泡
void soak() {
System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
}
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎 ");
}
//钩子方法,决定是否需要添加配料
boolean customerWantCondiments() {
return true;
}
}
2.纯豆浆,子类决定是否要添加配料。(添加配料的不用改动)
public class PureSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
//空实现
}
@Override
boolean customerWantCondiments() {
return false;
}
}
客户端调用
public class Client {
public static void main(String[] args) {
System.out.println("----制作红豆豆浆----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作花生豆浆----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
System.out.println("----制作纯豆浆----");
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}
结果
----制作红豆豆浆----
第一步:选择好的新鲜黄豆
加入上好的红豆
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
----制作花生豆浆----
第一步:选择好的新鲜黄豆
加入上好的花生
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
----制作纯豆浆----
第一步:选择好的新鲜黄豆
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
模板方法模式的注意事项和细节
- 一般模板方法都加上 final 关键字, 防止子类重写模板方法.
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理
- 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
优点
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
缺点
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大