模板模式 :解决某类事情的步骤有些是固定的,有些是会发生变化的,那么这时候我们可以为这类事情提供一个模板代码,从而提高效率。
通过定义一个算法骨架,而将算法中的步骤延迟到子类,这样子类就可以复写这些步骤的实现来实现特定的算法。
是类的一种行为,只需要准备一个抽象类,将逻辑用具体方法和构造函数的形式来表现,后声明一些抽象方法来迫使子类必须实现其逻辑,不同的子类可以实现不同的方法,从而可以让剩余的逻辑有不同的实现。即可以定义抽象的方法,让子类实现剩余的逻辑。
分类:
办理银行业务:
准备一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法交由子类实现剩余逻辑,用钩子方法给予子类更大的灵活性。最后将方法汇总构成一个不可改变的模版方法。
案例一:编写一个计算程序运行时间的模板。
抽象基类:
abstract class MyRuntime{
public final void getTime(){
long startTime = System.currentTimeMillis(); //记录开始的时间
code();
long endTime = System.currentTimeMillis(); //记录结束的时间.
System.out.println("运行时间 :"+ (endTime-startTime));
}
public abstract void code();
}
具体子类:
class Demo extends MyRuntime
{
public static void main(String[] args)
{
Demo d = new Demo();
d.getTime();
}
//code方法内部就写要计算运行时间的代码;
public void code(){
int i = 0;
while(i<100){
System.out.println("i="+i);
i++;
}
}
}
案例二:饮料的制法:
把水煮沸(boilWater)
冲饮料(brew)
把饮料倒进杯子(pourInCup)
加调味料(addCondiments)
抽象基类:Drinks
package com.hcx.pattern.template;
/**
* 抽象基类,为所有子类提供一个算法框架
* 饮料
* @author HCX
*
*/
public abstract class Drinks {
/**
* 使用final修饰,防止子类改变模版方法
* 制备饮料的模版方法
* 封装了所有子类共同遵循的算法框架
*/
public final void prepareDrinksTemplate(){
//步骤一:把水煮沸
boilWater();
//步骤二:冲饮料
brew();
//步骤三:把饮料倒进杯子
pourInCup();
//步骤四:加调味料
addCondiments();
}
/**
* 基本方法:把水煮沸
* 对所有子类,是一个共同的行为,不需要向子类开放;将变化的东西放在高层代码中。
*/
private void boilWater() {
System.out.println("把水煮沸");
}
/**
* 基本方法:将饮料倒入杯中
*/
private void pourInCup() {
System.out.println("将饮料倒入杯中");
}
/**
* 不同的情况,具体的实现不同,设计为抽象方法,需要在子类中可见,以便子类复写,提供具体的实现。
* 抽象的基本方法:加入调料
*/
protected abstract void addCondiments();
/**
* 抽象的基本方法:泡饮料
*/
protected abstract void brew();
}
具体子类:Coffee
package com.hcx.pattern.template;
/**
* 具体子类,提供了咖啡制备的具体实现
* @author HCX
*
*/
public class Coffee extends Drinks {
@Override
protected void brew() {
System.out.println("用沸水冲泡咖啡");
}
@Override
protected void addCondiments() {
System.out.println("加入糖和牛奶");
}
}
具体子类:OrangeJuice
package com.hcx.pattern.template;
/**
* 具体子类,提供了橙汁的具体实现
* @author HCX
*
*/
public class OrangeJuice extends Drinks{
@Override
protected void brew() {
System.out.println("准备橙子和榨汁机,把橙子丢入机器中榨汁");
}
@Override
protected void addCondiments() {
System.out.println("加入糖浆");
}
}
测试:
package com.hcx.pattern.template;
public class DrinksTest {
public static void main(String[] args) {
System.out.println("咖啡制备中");
Drinks drinks = new Coffee();
drinks.prepareDrinksTemplate();
System.out.println("咖啡好了");
System.out.println("*************************************");
System.out.println("橙汁制备中");
Drinks drinks2 = new OrangeJuice();
drinks2.prepareDrinksTemplate();
System.out.println("橙汁好了");
}
}
结果:
咖啡制备中
把水煮沸
用沸水冲泡咖啡
将饮料倒入杯中
加入糖和牛奶
咖啡好了
*************************************
橙汁制备中
把水煮沸
准备榨汁机和榨汁机,把橙子丢入机器中榨汁
将饮料倒入杯中
加入糖浆
橙汁好了
使用钩子方法使代码更灵活:
在制备橙汁时,不想加入糖浆;
修改Drinks类,在加入调味料的步骤进行判断,编写钩子函数:
package com.hcx.pattern.template;
/**
* 抽象基类,为所有子类提供一个算法框架
* 饮料
* @author HCX
*
*/
public abstract class Drinks {
/**
* 使用final修饰,防止子类改变模版方法
* 制备饮料的模版方法
* 封装了所有子类共同遵循的算法框架
*/
public final void prepareDrinksTemplate(){
//步骤一:把水煮沸
boilWater();
//步骤二:冲饮料
brew();
//步骤三:把饮料倒进杯子
pourInCup();
//步骤四:加调味料
if(wantCondiments()){
addCondiments();
}
}
/**
* Hook:钩子函数,提供一个默认或空的实现
* 具体的子类可以自行决定是否挂钩以及如何挂钩,即是否重写父类的钩子函数
* 根据个人喜好,是否加入调料
* @return
*/
protected boolean wantCondiments() {
return true;
}
/**
* 基本方法:把水煮沸
* 对所有子类,是一个共同的行为,不需要向子类开放;将变化的东西放在高层代码中。
*/
private void boilWater() {
System.out.println("把水煮沸");
}
/**
* 基本方法:将饮料倒入杯中
*/
private void pourInCup() {
System.out.println("将饮料倒入杯中");
}
/**
* 不同的情况,具体的实现不同,设计为抽象方法,需要在子类中可见,以便子类复写,提供具体的实现。
* 抽象的基本方法:加入调料
*/
protected abstract void addCondiments();
/**
* 抽象的基本方法:泡饮料
*/
protected abstract void brew();
}
在OrangeJuice中重写钩子函数时:
package com.hcx.pattern.template;
/**
* 具体子类,提供了橙汁的具体实现
* @author HCX
*
*/
public class OrangeJuice extends Drinks{
@Override
protected void brew() {
System.out.println("准备橙子和榨汁机,把橙子丢入机器中榨汁");
}
@Override
protected void addCondiments() {
System.out.println("加入糖浆");
}
/**
* 重写父类的钩子方法
* 不加入任何调料,纯正的橙汁
*/
@Override
protected boolean wantCondiments() {
return false;
}
}
测试类打印结果:
咖啡制备中
把水煮沸
用沸水冲泡咖啡
将饮料倒入杯中
加入糖和牛奶
咖啡好了
*************************************
橙汁制备中
把水煮沸
准备橙子和榨汁机,把橙子丢入机器中榨汁
将饮料倒入杯中
橙汁好了
总结:
案例三:不同职位的工作
AbstractWork:
public abstract class AbstractWork {
protected void getUp(){
System.out.println("起床");
}
//上班有各种交通工具,让子类灵活的实现
protected abstract void goToWork();
protected abstract void work();
protected abstract void getOffWork();
/**
* TemplateMethod:每个子类都拥有共同的执行步骤
* final:不能改变、继承
*/
public final void newDay(){
getUp();
goToWork();
work();
getOffWork();
}
}
BossWork:
public class BossWork extends AbstractWork{
@Override
protected void goToWork() {
System.out.println("老板开着奔驰去上班");
}
@Override
protected void work() {
System.out.println("老板分配工作给员工");
}
@Override
protected void getOffWork() {
System.out.println("老板开着奔驰回家");
}
}
StaffWork:
public class StaffWork extends AbstractWork{
@Override
protected void goToWork() {
System.out.println("员工挤地铁公交去上班");
}
@Override
protected void work() {
System.out.println("员工处理具体工作");
}
@Override
protected void getOffWork() {
System.out.println("员工加班到晚上十点,然后地铁和公交还是好挤,身心疲惫的回家了");
}
}
MyTest:
public class MyTest {
public static void main(String[] args) {
BossWork bossWork = new BossWork();
StaffWork staffWork = new StaffWork();
bossWork.newDay();
System.out.println("====================");
staffWork.newDay();
}
}
打印结果:
起床
老板开着奔驰去上班
老板分配工作给员工
老板开着奔驰回家
====================
起床
员工挤地铁公交去上班
员工处理具体工作
员工加班到晚上十点,然后地铁和公交还是好挤,身心疲惫的回家了
适用场景:
优点:
缺点:
分析处理各种日志
需求分析:
可抽象为如下的步骤:
根据不同的情况,在变化部分的前后提供一些函数来提供扩展。