深入理解设计模式-模板模式
个人认为模版模式是最接近于架构设计设计的一种设计模式,也是用起来最像做架构的一种设计模式。因为模版模式的含义就是设计一个整体的框架和模版套路,定义好架子之后,由子类去填充具体的实现逻辑,从而复用整个框架和模版。不记得在哪本书中看过,印象很深刻,框架和业务的区别或者说框架和普通类库函数的区别于框架代码是去调用业务,类库和函数的。而类库和函数是被调用的。
怎么理解呢?打个比方。我们所说的spring
是框架,mybatis
是框架(应该没有人说他们是类库吧)。当一个请求过来的时候,或者一个main
函数开始入口调用的时候,首先经过的是框架的代码。它是基层,基础设施。而后续你写的代码是依托于它的基础设施之上的,使用他的内部的一些变量和函数。所有你写的代码都是由他来调用你的。而类库函数,比如apache
的common
包,httpclient
包,这些类库,都是由你来调用他,由它来暴露API,调用类库或函数的API。
模版模式就是这样的一种设计模式,由继承的子类来实现父类的一些抽象方法,而这些抽象方法并不是单独执行,而是先调用了父类的方法,经过父类的逻辑后,再调用子类的逻辑。
现实案例
首先再讲一个现实中的案例帮助消化和理解一下,比如说煮饭吃。
平常的时候,我们煮米饭吃,偶尔煮糯米饭吃。
对于大米饭,我们步骤是
- 洗电饭煲
- 放入普通的大米,也就是粳(jīng)米
- 插电煮饭
而煮糯米饭,我们步骤是
- 洗电饭煲
- 放入糯米
- 放水插电煮饭
大家发现了,放入不同的米,得到的米饭不一样。但是不管是煮大米饭还是糯米饭,它们的整理流程是一样的
- 洗电饭煲
- 放米
- 插电煮饭
只有第二个步骤放米
是不一样的。这样,我们可以把这个整个步骤抽象出来,定义好煮饭的三部曲,但是具体吃什么饭,那都是由第二个步骤洗米放米来决定的。这个抽象出来的步骤就是模版,而洗米放米就是具体的实现。
简单实现
抽象类ZhuFan
public abstract class ZhuFan {
public void zhufan() {
System.out.println("洗电饭煲");
fangmi();
System.out.println("插电煮饭");
}
abstract void fangmi();
}
实现子类ZhuJingMiFan
public class ZhuJingMiFan extends ZhuFan {
@Override
void fangmi() {
System.out.println("放粳米");
}
}
实现子类ZhuNuoMiFan
public class ZhuNuoMiFan extends ZhuFan {
@Override
void fangmi() {
System.out.println("放糯米");
}
}
测试方法
public static void main(String[] args) {
// 今天想吃粳米饭
System.out.println("今天想吃粳米饭");
ZhuFan zhuFan = new ZhuJingMiFan();
zhuFan.zhufan();
System.out.println("-----------过了一段时间想吃糯米饭-------------");
// 想吃糯米饭
zhuFan = new ZhuNuoMiFan();
zhuFan.zhufan();
}
结果
今天想吃粳米饭
洗电饭煲
放粳米
插电煮饭
-----------过了一段时间想吃糯米饭-------------
洗电饭煲
放糯米
插电煮饭
源码案例分析
下面看下模版模式的一个案例,这个案例是分析JDK
源码中父类AbstractList
和它的子类ArrayList
,LinkedArrayList
。我们只分析一个添加方法,从这个点切入的详细讲解下模版模式的精髓。
首先看下AbstractList
的add
方法。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
// 省略其他的一些方法和变量...
// 添加一个元素
public boolean add(E e) {
// 在整个List长度的最后一位添加元素
add(size(), e);
// 在没处理报错的情况下返回true,操作成功
return true;
}
// 具体的添加方法
public void add(int index, E element) {
// 抛出异常,不支持的操作方法
throw new UnsupportedOperationException();
}
// 省略其他的一些方法和变量...
}
这里是个非常典型的模版设计模式的应用。仔细分析一下,在AbstractList
中设计了一个添加元素的模版方法boolean add(E e)
,我们大多数情况下也是调用的这个方法。在这个方法中定义了两个逻辑:
- 在
List
的最后添加元素,调用的是自身的另一个方法add(size(), e)
- 如果没有处理报错,直接返回为true,操作成功
而在抽象类的AbstractList
中add(int index, E element)
方法并没有任何实现,而是直接抛出了不支持操作的异常。这时候会不会有小伙伴说,我们平时调用并不会报这个错啊,为什么呢?因为我们用到的都是它的子类,比如ArrayList
,LinkedList
或者其他的子类,它们实现了add(int index, E element)
的方法。如果我自定义一个MyList
去继承AbstractList
,但是不重写add(int index, E element)
,那肯定会抛出UnsupportedOperationException()
了。当然这里扯的有点远了。我们还是回到正题上,首先看下ArrayList
的add(int index, E element)
的实现
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 省略其他的一些方法和变量...
public void add(int index, E element) {
// 合法性检查
rangeCheckForAdd(index);
// 确保数组容量能容纳现有数据的数量加上这个新元素的
ensureCapacityInternal(size + 1); // Increments modCount!!
// 数组数据移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将元素放入指定的索引位置的的值
elementData[index] = element;
// 数据长度加1
size++;
}
// 省略其他的一些方法和变量...
}
再看一下LinkedList
的add(int index, E element)
实现
// 注意这个AbstractSequentialList是继承了AbstractList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// 省略其他的一些方法和变量...
public void add(int index, E element) {
// 合法性检查
checkPositionIndex(index);
if (index == size)
// 位置为数据长度的大小,直接添加到最后
linkLast(element);
else
// 否则放入到指定元素的位置
linkBefore(element, node(index));
}
// 省略其他的一些方法和变量...
}
虽然ArrayList
和LinkedList
的数据结构不同,add(int index, E element)
的实现也不一样,但是不管实现类是ArrayList
或者LinkedList
,只要调用了add(E e)
,那一定是将元素添加到了数组或者链表的末尾,它们的父类AbstractList
定义好了逻辑就是在第一个的index为size()
,也就是是它们的结尾。这样的好处就是强制它的逻辑,如果直接调用add(E e)
,就是在最后添加而不是在最前添加或者中间位置添加。不会随着子类的扩展而改变这个逻辑。
Spring的Template
在Spring框架有一类以Template
结尾的都是模版模式的运用,比如:JdbcTemplate
,RestTemplate
,RedisTemplate
,RetryTemplate
,RabbitTemplate
,KafkaTemplate
。这些类往往实现了一类Operations
的接口,比如JdbcOperations
,RestOperations
,RedisOperations
,RetryOperations
,RabbitOperations
,KafkaOperations
。这个Operations
定义了一组和其相关的基本操作接口。Spring
在实现这些基本操作方法的时候也预留了一些钩子,可以进行自定义的操作实现。不过大多数时候,我们都只需要用这个模板类进行操作就够了,不过我们也可以继承它,实现一些定制化的操作,比如说StringRedisTemplate
,它就是对RedisTemplate
中序列化使用StringRedisSerializer
特例的一个子类。个人觉得这种Template
更多的表明这类操作是一种模版操作,而不仅仅是模版设计模式。它在突破了传统的模版模式的父类是抽象类的定义,是一个模版方式的变种。因为这些都非抽象类,所以可以直接new出来进行使用,但是又可以继承进行一些改造。
总结
本文从模版的理解,现实案例的演示,源码中的模版模式的运用和Spring
的Template
类的讲解,主要是说明了模版模式在代码中的重要性。因为模版模式是在很多源码中使用频繁和常用的设计模式,理解模板模式对于理解源码有很大的帮助。
在平时的工程代码中,对于有着某一类同样步骤的内容,也可以使用模版模式来定义好其主要的逻辑顺序,再由子类实现特殊逻辑,这样可以减少大量重复的代码。而且通过相关命名,在阅读代码的时候也可以很快明白作者的意图,做到事半功倍的效果。
转载自:https://juejin.cn/post/7241148626813698106