设计模式-模版方法模式
定义
模版方法模式(Template Method Pattern)又叫模版模式,是指定义一个操作中的算法的框架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤,属于行为型设计模式.
模版方法模式实际上是封装了一个固定流程,该流程由几个步骤组成,具体步骤可以由子类进行不同实现,从而让固定的流程产生不同的结果.它非常简单,其实就是类的继承机制,但它却是一个应用非常广泛的模式.模版模式的本质是抽象封装流程,具体进行实现.
模版方法模式的应用场景
当一个操作具有固定的流程时,由抽象固定流程步骤,具体步骤交给子类进行具体实现(固定的流程,不同的实现).
在我们的日常生活当中,模版方法非常的常见.比如以下流程:
再比如赵本山老师的经典小品中的一个段子,把大象放进冰箱需要几步?
那么,同学们,我们接着研究一下:把长颈鹿放进冰箱需要几步?还是3步吗?
-
-
-
-
-
从以上的案例中,我们不难总结出模版方法模式的适用场景:
1、一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现.
2、各子类中公公的行为被提取出来并集中到一个公共的父类中,从而避免代码重复.
首先,我们看一下模版方法的通用UML静态类图:
在UML类图中,我们不难看到,模版方法模式主要包含两种角色:
抽象模版(AbstractClass):抽象模版类,定义了一套算法框架/流程;
具体实现(ConcreteClass):具体实现类,对算法框架/流程中的某些步骤进行了实现.
模版方法模式中的钩子方法
我们继续以赵本山老师的冰箱装大象事件为例,通过代码来实现:
抽象模板类中有个钩子方法,这里解释一下.设计钩子方法的主要目的是用来干预执行流程,使得我们能够控制执行流程,是其更符合实际业务需求.钩子方法的返回值一般为适合条件分支语句的返回值(如:boolean、int等).小伙伴们可以根据自己的实际业务场景决定是否使用钩子方法.
通过这个案例,相信小伙伴们一定对模版方法及钩子方法有了一个初步的认识.为了加深理解,我们一起去探查另一个应用案例.
利用模版方法模式重构JDBC操作业务场景
创建一个模版累JdbcTemplate,封装所有的JDBC操作.以查询为例,每次查询的表不同,返回的数据结构也不一样.我们针对不同的数据,都要封装成不同的实体对象.而每个实体封装的逻辑都是不一样的,但封装前和封装后的处理流程是不变的,因此,我们可以使用模版方法模式来设计这样的业务场景.先创建约束ORM逻辑的接口RowMapper:
再创建封装了所有处理流程的抽象类JdbcTemplate:
希望通过以上两个案例的业务场景分析,能够帮助小伙伴们对模版方法模式有比较深入的理解.
模版方法模式在源码中的应用
先来看JDK中的AbstractList,来看代码:
我们看到get()是一个抽象方法,那么它的逻辑就是交给子类来实现,我们大家熟悉的ArrayList就是AbstractList的子类.同理,有AbstractList就有AbstractSet和AbstractMap,感兴趣的小伙伴们可以去看看这些的源码实现.还有一个每天都在用的HttpServlet,有3个方法:service()方法、doGet()方法和doPost()方法,都是模版方法的抽象实现.
在Mybatis框架也有一些经典的应用,我们来看一下BaseExecutor类,它是一个基础的SQL执行类,实现了大部分的SQL执行逻辑,然后把几个方法教给子类定制化完成,源码如下:
如:doUpdate()、doFlushStatements()、doQuery()、doQueryCursor()这几个方法就是由子类来实现的,那么BaseExecutor有哪些子类呢?我们来看一下它的类图:
我们再来看一下SimpleExecutor的doUpdate实现:
再来对比一下BatchExecutor的doUpdate实现:
通过对比,我们不难发现两者的差异.封装抽象流程,具体进行实现.感兴趣的小伙伴可以继续深入研究一波MyBatis源码,我们这里就不继续深挖啦.
模版方法模式的优点
1、利用模版方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性.
2、在不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性.
3、把不变的行为写在父类中,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则.
模版方法模式的缺点
1、类数目增加,每个抽象类都需要至少一个子类来实现,这样导致类的数量增加.
2、类数量的增加,间接地增加了系统实现的复杂度.
3、继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍.