Java设计模式(04-- 模板方法模式)
什么是模板方法模式?
模板方法就是为多种类似业务提供一个算法执行的统一框架,把这些业务中共同的部分抽取出来进行具体实现,而某些业务中特定的需求推迟到子类中进行重写实现。
模板方法模式如何实现?
场景实例:就拿制备咖啡和热茶两种业务进行比较,两者有共同的业务步骤如煮沸热水和把泡制好导入杯中,也有特定的业务步骤即个性化需求如泡制咖啡和茶的材料时间是不同的,成品后加入的调味品也不同。
用抽象基类定义算法框架 RefreshBeverage
1 package com.imooc.pattern.template; 2 3 /** 4 * 抽象基类,为其他子类提供一个算法框架 5 * 提神饮料 6 * @author zplogo 7 * 8 */ 9 public abstract class RefreshBeverage { 10 11 /* 12 * 制备饮料的模板方法 13 * 封装了所有子类所遵循的算法框架 14 */ 15 public final void prepareBeverageTemplate() { 16 //步骤一 将水煮沸 17 boilWater(); 18 //步骤二 泡制饮料 19 brew(); 20 //步骤三 将饮料倒入杯中 21 pourInCup(); 22 if(isCustomerWantsCondiments()){ 23 //步骤四 加入调味料 24 addCondiments(); 25 } 26 } 27 /* 28 * Hook 钩子函数,提供一个空的或者默认的实现 29 * 子类重写该方法,可以自行决定是否挂钩以及如何挂钩 30 */ 31 protected boolean isCustomerWantsCondiments() { 32 return true; 33 } 34 35 //因为将水煮沸和把饮料倒入杯中对所有子类是共同的行为,所以没必要向子类过多开放,所以方法定义为private,这样我们在进行子类编码时可以减少复杂度。 36 //这样不需要关注细枝末节,我们只需要关注我们特定业务的实现,这就是模板方法模式的好处。可以封装变与不变,将不变的固化在高层,隐藏其细节。 37 private void boilWater() { 38 System.out.println("将水煮沸"); 39 } 40 41 private void pourInCup() { 42 System.out.println("将饮料倒入杯中"); 43 } 44 45 /* 46 * 泡制饮料brew()和加入调料品addCondiments()这两个方法我们不知道它们在算法框架中的具体实现,因此定义为抽象方法,我们用protected进行修饰, 47 * 在子类中可见便于进行重写。 48 */ 49 protected abstract void brew(); 50 51 protected abstract void addCondiments(); 52 53 }
具体子类延迟实现步骤 Coffee
1 package com.imooc.pattern.template; 2 3 /** 4 * 提供制备咖啡的具体实现子类。 5 * 具体子类实现延迟步骤,满足特定的业务需求。 6 * @author zplogo 7 * 8 */ 9 public class Coffee extends RefreshBeverage { 10 11 protected void brew() { 12 System.out.println("步骤二 用沸水冲泡咖啡"); 13 } 14 15 protected void addCondiments() { 16 System.out.println("步骤四 加入糖和牛奶"); 17 } 18 19 }
钩子使子类更灵活 Tea
1 package com.imooc.pattern.template; 2 3 public class Tea extends RefreshBeverage { 4 5 protected void brew() { 6 System.out.println("步骤二 用80度热水浸泡茶叶5分钟"); 7 } 8 9 protected void addCondiments() { 10 System.out.println("步骤四 加入柠檬"); 11 } 12 13 protected boolean isCustomerWantsCondiments(){ 14 return false; 15 } 16 17 }
测试类 RefreshBeverageTest
1 package com.imooc.pattern.template; 2 3 public class RefreshBeverageTest { 4 5 public static void main(String[] args) { 6 System.out.println("制备咖啡中······"); 7 RefreshBeverage b1 = new Coffee(); 8 b1.prepareBeverageTemplate(); 9 System.out.println("咖啡好了········"); 10 11 //制备茶的测试代码 12 System.out.println("\n*********************************"); 13 System.out.println("制备茶水中······"); 14 RefreshBeverage b2 = new Tea(); 15 b2.prepareBeverageTemplate(); 16 System.out.println("茶水好了······"); 17 } 18 19 }
首先抽象基类中把相同的业务需求用具体的方法实现,对于只知道实现原则而不知道实现细节的业务我们推迟到子类中完成,用抽象方法进行定义。然后在基类中定义钩子方法,
子类可以重写钩子方法以满足个性化的需求,在然后把具体实现方法,抽象方法,钩子按业务逻辑组合成进一个模板方法中,特别注意模板方法用final进行修饰,因为需要遵循一个原则
就是子类可以改变父类中可变部分的业务逻辑但是不可以改变整体的业务逻辑,这就是所谓的好莱坞原则。
具体子类 1)需要实现父类中的抽象方法,以 满足个性化的需求 2)覆盖钩子方法
模板方法模式适用的业务场景
1)算法或者操作遵循相似的逻辑
2)重构时(把相同的代码抽取到父类中)
3)重要、复杂的算法,核心算法设计为模板方法
模板方法模式的优点
1)封装性好 2)复用性好 3)屏蔽细节 4)便于维护
模板方法模式的缺点
由于Java是单继承,我们面对一个旧系统进行重构的时候,原系统就有继承,我们想用模板方法重构再次引入继承就会比较困难。
模板方法模式的实际应用(行业案例)
实例:运营商需要分析用户需求产生的大量日志。
同时为了满足个性化需求,我们在处理单行日志的时候可以加入钩子方法。