Java设计模式-模板方法

模板(Template Method)方法模式,在23种设计模式中属于类行为型模式。

模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

模式介绍

无论是对后台或者前端开发人员,相信模板这个词应该都不会感到陌生。前端的模板框架template.js,初始学jsp时include指令或者include动作,或者springboot开发中使用的freemarker或者Thymeleaf等,在这些模板代码的使用中,模板的存在主要是为了代码片段的复用。相比较而言,模板方法模式的重点反而并不是为了代码片段的使用,更倾向于架构层面的复用。

模板方法模式是为了创建一个算法模板的,其实这个模板就是一个方法。在该方法中将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法结构不变,同时由子类提供部分实现。

模板方法是一种行为型设计模式。一般行为型设计模式分为两种:类行为模式和对象行为模式,类行为模式使用的是继承实现行为分配,对象行为模式使用的是对象组合而非类继承。模板方法属于类行为模式,因此它是一种基于继承的代码复用模式。

适用性

  • 一次性实现一个算法的不变部分,将可变的行为留给子类去实现。
  • 各子类的公共行为被抽取并集中到一个公共父类中以避免代码重复。
  • 控制子类扩展。模板方法只在特定点调用“hook”操作,这样就只允许在这些点进行扩展。

示例介绍

在这里我们借助《Head First Design Pattern》中的泡茶和煮咖啡的示例做介绍。

咖啡
把水煮沸把水煮沸
用沸水冲泡茶叶用沸水冲咖啡粉
把茶倒进杯子把咖啡倒进杯子
加柠檬加糖和牛奶

将茶和咖啡的操作步骤泛化。

  1. 把水煮沸
  2. 冲泡
  3. 把饮料倒进杯子
  4. 加调料
public abstract class CaffeineBeverage {
  
	final void prepareRecipe() {
		boilWater(); //煮沸水
		brew(); // 冲泡饮料
		pourInCup(); // 将饮料倒入杯子
		addCondiments(); // 添加调料
	}
 
	abstract void brew();
  
	abstract void addCondiments();
 
	void boilWater() {
		System.out.println("Boiling water");
	}
  
	void pourInCup() {
		System.out.println("Pouring into cup");
	}
}
public class Tea extends CaffeineBeverage {
	public void brew() {
		System.out.println("Steeping the tea");
	}
	public void addCondiments() {
		System.out.println("Adding Lemon");
	}
}
Tea tea = new Tea();
Coffee coffee = new Coffee();

System.out.println("\nMaking tea...");
tea.prepareRecipe();

System.out.println("\nMaking coffee...");
coffee.prepareRecipe();

添加调料是消费者自主行为,有些消费者可能不喜欢额外的添加品,这样我们可以将添加调料作为一个可选项,由消费者自己选择是否需要添加调料。

public abstract class CaffeineBeverageWithHook {
	final void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		if (customerWantsCondiments()) {
			addCondiments();
		}
	}
	
	// 该方法就是一个钩子方法
	// 提供一个默认实现,返回值为true
	// 子类可以选择是否重写该方法
	boolean customerWantsCondiments() {
		return true;
	}
}

模板方法模式的一般示例如下:

public abstract class AbstractClass {

	final void templateMethod(){
		primitiveOperation01();
		primitiveOperation02();
		concreteMethod();
		hook();
	}
	
	abstract void primitiveOperation01();
	
	abstract void primitiveOperation02();
	
	final void concreteMethod(){
		// 具体方法实现
	}
	
	void hook(){}
	
}

模式分析

模板方法模式类图如下:

  • AbstractClass:抽象类。用来定义算法骨架和原语操作,在这个类里面,还可以提供算法中通用的实现。
  • ConcreteClass:具体实现类。用来实现算法骨架中的某些步骤,完成子类相关的功能。

模板方法templateMethod()在实现算法的过程中,用到了两个原语操作primitiveMethod()。模板方法本身和原语操作的具体实现之间解耦了。

模板方法为了固定算法框架,一般设计为final类型的,主要是为了防止子类重写算法框架。

模板方法中的调用的方法并不全是抽象方法,有一些conreteMethod(),一般讲具体方法定位为final类型的,这样子类只可以调用该类型方法,却不可以重写这些方法。

模板方法中还有一类方法,这种方法即不是抽象方法,也不会定义为final类型的,被称为钩子方法(Hook Method)或者空方法。子类可以视情况要不要覆盖该类型方法。

模板方法中原语操作要尽可能的少,定义模板方法的目的就是为了减少子类实现算法的步骤。如果原语操作过多,那么子类需要实现的操作也就越多。

模板方法其实就是依赖抽象类实现的一种类型的设计模式,但是它仅仅是抽象类应用中的一个场景。抽象类不仅仅是为了抽象模板方法中的原语操作,抽象类也会抽象许多子类需要完成的共有操作,这些共有操作并不一定是某个算法框架的一个组成部分。

好莱坞原则

通过模板方法设计模式可以很容易关联到软件设计中的一个原则-好莱坞原则。

别调用(打电话给)我们,我们会调用(打电话给)你。

好莱坞原则主要是为了防止环状依赖问题的,环状依赖就是说高层组件依赖低层组件,而低层组件又依赖高层组件。

在好莱坞原则之下,我们允许底层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些底层组件。

好莱坞原则强调高层对低层的主动作用,即低层应该只管好自己的工作(具体实现),而高层自有它自己的工作(这就是管理低层的逻辑们,或者说从client到具体实现的一系列中间逻辑),在不需要到某个低层的时候,高层并不会调用到这个具体低层,低层永远不需要向高层作出表示,说它需要被调用。

模式优点

可以实现代码复用,模板方法通过将不变的共有的行为抽取到父类中,去除了子类中重复的代码。

将不同的代码放入不同的子类中,子类实现算法的细节,有利于算法的扩展。

通过父类调用子类实现的操作,通过子类增加扩展父类的行为,符合“开闭原则”。

模式缺点

模板方法模式体现了设计模式的通病,类的膨胀,大量衍生类的创建。另外,子类的执行结果影响到父类的行为,增加了代码的阅读难度。

与其它模式对比

模板方法模式与工厂方法模式都可以让具体的实现延迟到子类中,但是工厂方法属于创建型模式,它提供了一种创建复杂对象的方式。模板方法属于行为型模式,父类中已经定义了某个算法的框架,子类只需要实现某些特定的方法即可。

模板方法模式与策略模式的意图不一样,模板方法模式是定义一个算法的大纲,而由其子类定义其中某些步骤的内容。策略模式封装可互换的行为,每一种行为都是一种算法,这些算法之间是平等的,可以选择任何一个算法执行某种行为。

策略模式是对象行为模式,采用的是对象组合方式实现,模板方法模式是类行为模式,采用类继承方式实现。

小结

模板方法模式其实是一种很常见的设计模式,在一些框架中尤为常见。一般框架层会控制如何做事情,而使用框架的开发人员只需要实现框架中指定的具体步骤即可。

在学习模板方式模式时,需要注意哪些行为需要抽象为原语方法,哪些行为可以抽象为final的具体方法,什么时候可以使用hook方法。相比较其它设计模式而言,模板方法模式是一种非常基础的设计模式,只需要对抽象类与接口有一定了解很很容易理解。

评论

您确定要删除吗?删除之后不可恢复