模版方法模式

模版方法模式

一、什么是模版方法模式

模板模式 :解决某类事情的步骤有些是固定的,有些是会发生变化的,那么这时候我们可以为这类事情提供一个模板代码,从而提高效率。
通过定义一个算法骨架,而将算法中的步骤延迟到子类,这样子类就可以复写这些步骤的实现来实现特定的算法。

是类的一种行为,只需要准备一个抽象类,将逻辑用具体方法和构造函数的形式来表现,后声明一些抽象方法来迫使子类必须实现其逻辑,不同的子类可以实现不同的方法,从而可以让剩余的逻辑有不同的实现。即可以定义抽象的方法,让子类实现剩余的逻辑。

分类:

  • 抽象模版:定义的数量和类型,定义了一个抽象操作让子类实现,定义并实现了一个模版方法,该模版方法一般是一个具体方法,同时给出了顶层逻辑的框架,具体的步骤在相应的抽象操作中,具体的业务逻辑延迟到子类实现。(只有一个抽象基类)
  • 具体模版:模版方法的数量,实现父类定义的一个或多个抽象方法,每个抽象模版的角色都可以由任意多个具体模版角色与之对应。即不是一对一,而是多对多。
1.生活中的模版:

办理银行业务:

  1. 进门取号
  2. 填写单据(每个客户填写的单据都不一样,因业务不同而不同)
  3. 等待叫号
  4. 窗口办理
2.使用场景
  • 多个子类公有的方法,并且逻辑基本相同时
  • 重要、复杂的算法,可以把核心算法设计为模版方法
  • 重构时,模版方法模式是一个经常使用的模式
3.UML结构图:

模版方法模式UML.png

二、如何实现模版方法模式

1.模版方法模式的实现要素

准备一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法交由子类实现剩余逻辑,用钩子方法给予子类更大的灵活性。最后将方法汇总构成一个不可改变的模版方法。

2.模版模式的步骤
  1. 先写出解决该类事情其中 的一件的解决方案。
  2. 分析代码,把会发生变化的代码抽取出来独立成一个方法。把该方法描述成一个抽象的方法。
  3. 使用final修饰模板方法,防止别人重写你的模板方法。
3.具体实现

案例一:编写一个计算程序运行时间的模板。

  • 记录开始时间
  • 代码的执行过程
  • 记录结束时间
  • 运行时间=结束时间-开始时间

抽象基类:

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;
	}
}

测试类打印结果:

咖啡制备中
把水煮沸
用沸水冲泡咖啡
将饮料倒入杯中
加入糖和牛奶
咖啡好了
*************************************
橙汁制备中
把水煮沸
准备橙子和榨汁机,把橙子丢入机器中榨汁
将饮料倒入杯中
橙汁好了

总结:

模版模式总结.png

案例三:不同职位的工作

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();
	}

}

打印结果:

起床
老板开着奔驰去上班
老板分配工作给员工
老板开着奔驰回家
====================
起床
员工挤地铁公交去上班
员工处理具体工作
员工加班到晚上十点,然后地铁和公交还是好挤,身心疲惫的回家了

三、模版方法模式的适用场景及优缺点

适用场景:

  • 算法或操作遵循相似的逻辑
  • 重构时(把相同的代码抽取到父类中)
  • 重要、复杂的算法,核心算法设计为模版算法

优点:

  • 封装性好
  • 复用性好
  • 屏蔽细节
  • 便于维护

缺点:

  • 单继承

四、实例分析

分析处理各种日志

需求分析:

日志需求分析.png

可抽象为如下的步骤:

  • 获得文件
  • 打开文件
  • 读取日志结构
  • 处理单行日志(会变化的部分,延迟到子类实现)
  • 清理工作

根据不同的情况,在变化部分的前后提供一些函数来提供扩展。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值