设计模式之【模板方法模式】
在平时开发中,你是否遇到过这种情况:确定了业务逻辑的关键步骤及其执行顺序,但是某些步骤的具体实现还未知,或者某些步骤的实现与具体的环境有关。
比如,我们去银行办理业务的时候,一般都是按照这个步骤来的:取号、排队等候、办理业务、评价。这是一个固定的流程,但是其中办理业务这个步骤是因人而异的,他们可能办理存款业务、转账业务或者是贷款业务。还有报销的过程,在医院挂号看病等这些例子,都是有一套固定的流程,但是在某些步骤上有不同的实现。
就像我们平常使用的简历模板,论文模板,在模板里面写不同的内容。
你看,这不就是我们设计模式中的模板方法模式嘛?
模板方法模式
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
注:这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法框架就是“模板”,包含算法框架的方法就是“模板方法”。
-
AbstractClass:抽象类。用来定义算法框架和抽象操作,具体的子类通过重定义这些抽象操作来实现一个算法的各个步骤。在这个类里面,还可以提供算法中通用的实现。
-
ConcreteClass:具体实现类。用来实现算法框架中的某些步骤,完成与特定子类相关的功能。
走,我们去银行看看。
1 public abstract class AbstractClass { 2 // 模板方法(不可以被覆盖,所以是final) 3 public final void TemplateMethod() { 4 TakeANumber(); // 取号 5 QueueUp(); // 排队 6 Business(); // 办理业务 7 Evaluate(); // 评价 8 } 9 10 // 具体方法(也可以设置成抽象方法,推迟到子类实现) 11 protected void TakeANumber() { 12 System.out.println("取号成功!"); 13 } 14 15 protected void QueueUp() { 16 System.out.println("排队等候!"); 17 } 18 19 protected void Evaluate() { 20 System.out.println("您的业务办理完成,请您对本次服务做出评价!"); 21 } 22 23 // 特定方法:银行业务办理 24 protected abstract void Business(); 25 }
现在有两个顾客,分别去银行办理存款业务和转账业务。
1 public class ConcreteClassA extends AbstractClass { 2 protected void Business() { 3 System.out.println("您好,麻烦帮我办理存款业务!"); 4 } 5 } 6 7 public class ConcreteClassB extends AbstractClass { 8 protected void Business() { 9 System.out.println("您好,麻烦帮我办理转账业务!"); 10 } 11 } 12 public class Demo { 13 public static void main(String[] args) { 14 AbstractClass customerA = new ConcreteClassA(); 15 customerA.TemplateMethod(); 16 17 AbstractClass customerB = new ConcreteClassB(); 18 customerB.TemplateMethod(); 19 } 20 } 21 22 // 客户A 23 取号成功! 24 排队等候! 25 您好,麻烦帮我办理存款业务! 26 您的业务办理完成,请您对本次服务做出评价! 27 28 // 客户B 29 取号成功! 30 排队等候! 31 您好,麻烦帮我办理转账业务! 32 您的业务办理完成,请您对本次服务做出评价!
抽象模板中的基本方法尽量设计为protected或private类型,符合【迪米特法则】,不需要暴露的属性或方法尽量不要设置为public。实现类若非必要,尽量不要扩大父类中的访问权限。
模板方法模式主要是用来解决复用和扩展两个问题。
模板方法模式把一个算法中不变的流程抽象到父类的模板方法TemplateMethod()中,将可变的部分Business()留给子类ConcreteClassA和ConcreteClassB来实现,所有的子类都可以复用父类中模板方法定义的流程代码。
模板方法模式的第二大作用是扩展,这里所说的扩展,并不是指代码的扩展性,而是指框架的扩展性。基于这个作用,模板方法模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。
模板方法模式的优点
-
封装不变部分,扩展可变部分
把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。
-
提取公共部分代码,便于维护
-
行为由父类控制,子类实现
基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开-闭原则。
模板方法模式的缺点
-
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
-
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它降低了代码的可阅读性。
-
由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
模板方法模式的应用场景
1、算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
2、当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作,最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
3、当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样只允许在这些点进行扩展。
模板方法模式在开源代码中的应用
JDK 中 java.util.AbstractList 抽象集合类,用到了模板方法模式,定义了留给子类实现的 add 方法和模板方法 addAll。
1 public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { 2 3 //新增元素的方法,留给子类实现 4 public void add(int index, E element) { 5 throw new UnsupportedOperationException(); 6 } 7 8 //模板方法。新增目标集合类的所有元素,默认调用 add 方法实现,也可以被子类重写 9 public boolean addAll(int index, Collection<? extends E> c) { 10 rangeCheckForAdd(index); 11 boolean modified = false; 12 for (E e : c) { 13 add(index++, e); 14 modified = true; 15 } 16 return modified; 17 } 18 }
AbstractList 每个子类内部的数据结构可能并不相同,对 add 方法的实现延迟到子类,每个子类可以按照自己的逻辑实现。(当然,addAll 方法也可以被覆盖)
总结
定义一套流程模板,根据需要实现模板中的操作。
参考
极客时间专栏《设计模式之美》