一、概念
模板设计模式—基于抽象类的,核心是封装算法
模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供具体实现,使用模板方法模式有Spring、Servlet、AQS等
在一个方法中定义一个算法的骨架,并将一些具体步骤延迟到子类中实现。模板模式使得子类在不改变算法结构的基础上,重新具体定义算法中的某些步骤,提高代码复用性。
优点
使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求
缺点
如果算法骨架有修改的话,则需要修改抽象类
步骤
1、先将主流程框架逻辑(清点商品/计算价目/结算/送货上门)设计完成
2、再实现各模块小步骤。
3、不能确实的步骤,作为虚拟方法,甩锅给子类实现。
4、子类只需要聚焦自己的小步骤逻辑。
讲模板设计模式之前,我们用代码来实现咖啡和茶制作的类:
普通类写法
public class MakeDrink {
public static void main(String[] agrs) {
Coffee coffee = new Coffee();
coffee.makeCoffee();
System.out.println("~~~~~~");
Tea tea = new Tea();
tea.makeTea();
}
}
//制作咖啡
class Coffee {
//make coffee
void makeCoffee() {
boilWater();
brewCoffeeGrings();
pourInCup();
addSugarAndMilk();
}
// setp1:
public void boilWater() {
System.out.println("将水煮沸");
}
// setp2:
public void brewCoffeeGrings() {
System.out.println("冲泡咖啡");
}
// setp3:
public void pourInCup() {
System.out.println("把咖啡倒进杯子中");
}
// setp4:
public void addSugarAndMilk() {
System.out.println("加糖和牛奶");
}
}
// 制作茶
class Tea {
//make tea
void makeTea() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
// setp1:
public void boilWater() {
System.out.println("将水煮沸");
}
// setp2:
public void steepTeaBag() {
System.out.println("浸泡茶叶");
}
// setp3:
public void pourInCup() {
System.out.println("把茶倒进杯子中");
}
// setp4:
public void addLemon() {
System.out.println("加柠檬");
}
}
我们在coffee和Tea两个类中发现了重复代码,因此我们需要重新理一下我们的设计。
既然茶和咖啡是如此的相似,因此我们应该将共同的部分抽取出来,放进一个基类中。从冲泡法入手。观察咖啡和茶的冲泡法我们会发现,两种冲泡法都采用了相同的算法:
- 将水煮沸
- 用热水泡饮料
- 把饮料倒进杯子
- 在饮料内加入适当的调料
实际上,浸泡(steep)和冲泡(brew)差异并不大。因此我们给它一个新的方法名称brew(),这样我们无论冲泡的是何种饮 料都可以使用这个方法。
同样的,加糖、牛奶还是柠檬也很相似,都是在饮料中加入其它调料,因此我们也给它一个通用名称addCondiments()。
另外,不管泡咖啡还是冲茶,其实都是做饮品,我们改算法的名字为makingDrinks
模版方法设计模式
/**
* 模板方法设计模式
*/
public class TemplateMethodPatternTest1 {
/**
* 模版方法抽象类
*/
abstract class DrinkProduction {
/**
* 生产饮料的步骤
* final禁止子类重写
*/
public final void makingDrinks() {
boilWater();
brew();
pourInCup();
addCondiments();
}
/**
* 步骤一:烧水
*/
public void boilWater() {
System.out.println("将水煮沸");
}
/**
* 步骤二:加主料
* 强制子类实现
*/
public abstract void brew();
/**
* 步骤三:倒入茶杯
*/
public void pourInCup() {
System.out.println("倒入杯子里");
}
/**
* 步骤四:加佐料
* 强制子类实现
*/
public abstract void addCondiments();
}
// 做咖啡
class Coffee extends DrinkProduction {
@Override
public void addCondiments() {
System.out.println("添加牛奶和糖");
}
@Override
public void brew() {
System.out.println("加入coffee煮");
}
}
// 做茶
class Tea extends DrinkProduction {
@Override
public void addCondiments() {
System.out.println("加入柠檬");
}
@Override
public void brew() {
System.out.println("加入茶叶煮");
}
}
public void test(){
Coffee coffee = new Coffee();
coffee.makingDrinks();
System.out.println("~~~~~~");
Tea tea = new Tea();
tea.makingDrinks();
}
public static void main(String[] args) {
TemplateMethodPatternTest1 tem = new TemplateMethodPatternTest1();
tem.test();
}
}
uml图
在模板设计模式下还有一种钩子用法
钩子方法是一类"默认不做事的方法" ,子类可以视情况决定要不要覆盖它们。
比如说,泡茶的时候,最后一步不用加柠檬
/**
* 有钩子的模版方法设计模式
*/
public class TemplateMethodPatternTest2 {
/**
* 模版方法抽象类
*/
abstract class DrinkProduction {
/**
* 生产饮料的步骤
* final禁止子类重写
*/
public final void makingDrinks() {
boilWater();
brew();
pourInCup();
//钩子决定是否调用
if(isAddCondiments()) {
addCondiments();
}
}
/**
* 步骤一:烧水
*/
public void boilWater() {
System.out.println("将水煮沸");
}
/**
* 步骤二:加主料
*/
public abstract void brew();
/**
* 步骤三:倒入茶杯
*/
public void pourInCup() {
System.out.println("倒入杯子里");
}
/**
* 步骤四:加佐料
*/
public abstract void addCondiments();
/**
* 钩子写法
* 如果不需要就对这个方法重写
* @return
*/
public boolean isAddCondiments(){
return true;
}
}
class Coffee extends DrinkProduction {
@Override
public void brew() {
System.out.println("加入coffee继续煮");
}
@Override
public void addCondiments() {
System.out.println("添加牛奶和糖");
}
}
class Tea extends DrinkProduction {
@Override
public void brew() {
System.out.println("加入茶叶煮");
}
// 不需要实现,空方发即可
@Override
public void addCondiments() {
}
@Override
public boolean isAddCondiments(){
return false;
}
}
public void test(){
Coffee coffee = new Coffee();
coffee.makingDrinks();
System.out.println("~~~~~~");
Tea tea = new Tea();
tea.makingDrinks();
}
public static void main(String[] args) {
TemplateMethodPatternTest2 tem = new TemplateMethodPatternTest2();
tem.test();
}
}