前言
软件设计模式,其实就是指在某种场景下,一套符合要求、具有良好拓展性的代码设计。接口、类之间怎样组合、怎样关联,使得代码即满足需求,又耦合性低。当然了,由于具体的场景不尽相同,所以各种设计模式也只是一个提供一个思路、概要,具体细节需要根据具体的场景变通。
java语言,由于其悠久的历史和庞大良好的生态,使用人数众多,前人总结出了23中设计模式。这23中设计模式可分为3中类型:
- 创建型:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
- 结构性:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享
元模式、代理模式 - 行为型:模版方法模式、命令模式、访问者模式、迭代器模式、观察者
模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模
式、策略模式、职责链模式(责任链模式)
前情引入
今天咱们要说的是 责任链模式,咱们先引入一个具体的场景:
学校OA系统的采购审批项目:需求是
- 采购员采购教学器材
- 如果金额 小于等于5000, 由系主任审批 (0<=x<=5000)
- 如果金额 小于等于10000, 由院长审批 (5000<x<=10000)
- 如果金额 小于等于30000, 由副校长审批 (10000<x<=30000)
- 如果金额 超过30000以上,有校长审批 ( 30000<x)
咱们该怎么设计呢?
不使用设计模式
首先分析,可能存在那些类?按照我目前的水平,我首先想到,有四个类,因为有系主任、院长、副校长、校长四个角色,然后我还能想到,由于这四个角色都具有审批功能,所以还应该有一个接口Approvable,接口中存在一个approve方法,系主任、院长、副校长、校长四个角色都实现这个接口。能想到的就这么多。另外还需要个请求类发起请求,具体代码如下:
- Approvable
public interface Approvable {
/** 抽象的审批方法,由具体的信息审批者实现 */
public abstract void approve(PurchaseRequest request);
}
- 系主任
public class Department implements Approvable {
private final String name;
public Department(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void approve(PurchaseRequest request) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.name));
}
}
- 院长
public class College implements Approvable {
private final String name;
public College(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void approve(PurchaseRequest request) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.name));
}
}
- 副校长
public class ViceSchoolMaster implements Approvable {
private final String name;
public ViceSchoolMaster(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void approve(PurchaseRequest request) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.name));
}
}
- 校长
public class SchoolMaster implements Approvable {
private final String name;
public SchoolMaster(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void approve(PurchaseRequest request) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.name));
}
}
- 请求类
import java.util.UUID;
public class PurchaseRequest {
/** 请求类型 */
private final int type;
/** 序号 */
private final int number;
/** 金额 */
private final double amount;
/** id */
private final String id;
public PurchaseRequest(int type, int number, double amount) {
this.type = type;
this.number = number;
this.amount = amount;
this.id = UUID.randomUUID().toString().replace("-", "");
}
public int getType() {
return type;
}
public int getNumber() {
return number;
}
public double getAmount() {
return amount;
}
public String getId() {
return id;
}
}
虽然在这里系主任、院长、副校长和校长的代码是一样的,但是不能将他们设计成一个类,因为这个四个角色肯定有不同的地方,而且实现approve方法我也只是简单的打印,实际上肯定有各自的操作。
这么设计之后,怎么使用呢?
public class Client {
public static void main(String[] args) {
// 构造请求
PurchaseRequest request = new PurchaseRequest(1, 1, 8000);
// 根据请求的金额决定由谁处理
if (request.getAmount() <= 5000) {
Department department = new Department("系主任");
department.approve(request);
} else if (request.getAmount() <= 10000) {
College college = new College("院长");
college.approve(request);
}else if (request.getAmount() <=30000){
ViceSchoolMaster viceSchoolMaster = new ViceSchoolMaster("副校长");
viceSchoolMaster.approve(request);
}else {
SchoolMaster schoolMaster = new SchoolMaster("校长");
schoolMaster.approve(request);
}
}
}
当然,这样设计能满足需求,但是这样做有什么问题呢?
- 假设现在副校长的审批权力调整,10000到50000都可以由副校长审批。那么,我们不仅需要改动副校长中的审批代码,还需要修改客户端中的if……else If……的判断逻辑。
- 假设,现在需要增加一个审批人,指导员,那么还是需要到客户端中修改if……else if的判断逻辑。
所以现在存在的问题就是:客户端必须完全知晓,存在的所有审批级别以及每个审批级别的审批金额,这是很严重的耦合关系,不利于系统的扩展。那如果使用责任链设计模式,又是如何设计呢?
使用设计模式
使用责任链设计模式,首先,和之前分析的一样,需要存在四个角色,也就是四个类,而且这个四个类有共同的方法,所以也需要实现同一个抽象方法,但是这里不再是声明成接口,而是声明成了抽象类,为什么要这么做,这是因为,需要在抽象类中定义一个变量,这个变量引用下一个审批者,如果当前审批者无法审批此次金额,就将任务交给下一个审批者。代码:
- 审批者抽象类
public abstract class Approver {
private final String name;
/** 下一个审批者,如果当前审批者无法审批,就交给下一个审批者,其实这个抽象类也可以声明成接口,在具体的实现类中再声明一个Approver变量 */
public Approver nextApprover;
/** 抽象的审批方法,由具体的信息审批者实现 */
public abstract void approve(PurchaseRequest request);
public Approver(String name) {
this.name = name;
}
public Approver(String name, Approver nextApprover) {
this.name = name;
this.nextApprover = nextApprover;
}
public String getName() {
return name;
}
public Approver getNextApprover() {
return nextApprover;
}
public void setNextApprover(Approver nextApprover) {
this.nextApprover = nextApprover;
}
}
- 系主任
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {
super(name);
}
/** 这里必须手动声明构造方法,并且调用父类中带参的构造方法,因为父类没有无参的构造方法,所以无法本类中默认的无参构造无法调用父类中的无参构造 */
public DepartmentApprover(String name, Approver nextApprover) {
super(name, nextApprover);
}
@Override
public void approve(PurchaseRequest request) {
if (request.getAmount() <= 5000) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.getName()));
}else {
// 当前审批者无法处理,交由下一级审批者处理
this.getNextApprover().approve(request);
}
}
}
- 院长
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
super(name);
}
/** 这里必须手动声明构造方法,并且调用父类中带参的构造方法,因为父类没有无参的构造方法,所以无法本类中默认的无参构造无法调用父类中的无参构造 */
public CollegeApprover(String name, Approver nextApprover) {
super(name, nextApprover);
}
@Override
public void approve(PurchaseRequest request) {
if (request.getAmount() > 5000 && request.getAmount() <= 10000) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.getName()));
}else {
this.nextApprover.approve(request);
}
}
}
- 副校长
public class ViceSchoolMasterApprover extends Approver {
public ViceSchoolMasterApprover(String name) {
super(name);
}
/** 这里必须手动声明构造方法,并且调用父类中带参的构造方法,因为父类没有无参的构造方法,所以无法本类中默认的无参构造无法调用父类中的无参构造 */
public ViceSchoolMasterApprover(String name, Approver nextApprover) {
super(name, nextApprover);
}
@Override
public void approve(PurchaseRequest request) {
if (request.getAmount() > 10000 && request.getAmount() <= 30000) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.getName()));
}else {
this.getNextApprover().approve(request);
}
}
}
- 校长
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {
super(name);
}
/** 这里必须手动声明构造方法,并且调用父类中带参的构造方法,因为父类没有无参的构造方法,所以无法本类中默认的无参构造无法调用父类中的无参构造 */
public SchoolMasterApprover(String name, Approver nextApprover) {
super(name, nextApprover);
}
@Override
public void approve(PurchaseRequest request) {
if (request.getAmount() > 30000 ) {
System.out.println(String.format("请求编号【%s】,金额为【%s】,被【%s】处理",request.getId(),
request.getAmount(),this.getName()));
}else {
this.getNextApprover().approve(request);
}
}
}
- 请求类
public class PurchaseRequest {
/** 请求类型 */
private final int type;
/** 序号 */
private final int number;
/** 金额 */
private final double amount;
/** id */
private final String id;
public PurchaseRequest(int type, int number, double amount) {
this.type = type;
this.number = number;
this.amount = amount;
this.id = UUID.randomUUID().toString().replace("-", "");
}
public int getType() {
return type;
}
public int getNumber() {
return number;
}
public double getAmount() {
return amount;
}
public String getId() {
return id;
}
那如果这样设计,又该怎样使用呢?与不使用设计模式,使用起来又有什么区别呢?
使用方式大抵有两种,单链或者环形,链如下:
public class Client {
public static void main(String[] args) {
PurchaseRequest request = new PurchaseRequest(1, 1, 8000);
// 如果这样构建,属于单链,那么请求至少得从满足的那个处理者前期开始,比如,我8000的请求,只能用系主任、院长处理。
// 不能用副校长、 校长处理,那样会走到最后院长那里,报npe。如果不清楚关系,那么从第一级处理准没错,也是没问题的。
DepartmentApprover approver = new DepartmentApprover("年级主任1",
new CollegeApprover("院长1",
new ViceSchoolMasterApprover("副校长1",
new SchoolMasterApprover("校长1", null))));
approver.approve(request);
// 这样构建,属于环链,请求从链中任何一环开始都可以
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("校长2");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("副校长2",schoolMasterApprover);
CollegeApprover collegeApprover = new CollegeApprover("院长2",viceSchoolMasterApprover);
DepartmentApprover departmentApprover = new DepartmentApprover("系主任2",collegeApprover);
schoolMasterApprover.setNextApprover(departmentApprover);
schoolMasterApprover.approve(request);
}
}
这样一通操作下来,好像也没简便多少,而且,我客户端使用,还是需要知道所有审批之间的级别关系,不然没法构建链。
其实完全可以再封装,不要让 使用者来构建链:
public class ApproverFactory {
/** 获取审核单链 */
public static Approver createSingleStrandedApprover() {
// 如果这样构建,属于单链,那么请求至少得从满足的那个处理者前期开始,比如,我8000的请求,只能用系主任、院长处理。
// 不能用副校长、 校长处理,那样会走到最后院长那里,报npe。如果不知道,那么就像下面这样,从第一级处理,也是没问题的。
return new DepartmentApprover("年级主任1",
new CollegeApprover("院长1",
new ViceSchoolMasterApprover("副校长1",
new SchoolMasterApprover("校长1", null))));
}
public static Approver createCyclicStrandedApprover() {
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("校长2");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("副校长2",schoolMasterApprover);
CollegeApprover collegeApprover = new CollegeApprover("院长2",viceSchoolMasterApprover);
DepartmentApprover departmentApprover = new DepartmentApprover("系主任2",collegeApprover);
schoolMasterApprover.setNextApprover(departmentApprover);
// 这里返回 schoolMasterApprover、viceSchoolMasterApprover、collegeApprover、departmentApprover都可以
return schoolMasterApprover;
}
}
一探源码
了解了责任链设计模式,我们来看看mybatis中的经典实践:mybatis二级缓存的设计。mybatis一共有两级缓存,其中作用域更大的是二级缓存,二级缓存是跨线程的,关于mybatis的缓存,读者自行了解,此处不再赘述。
既然我们要观摩人家的设计,我们就得先了解有哪些功能需要实现。缓存,有哪些功能需要实现呢?
- 存储,缓存缓存,肯定是要找地方存储起来,所以存储需要实现。基于内存的缓存,还需要考虑持久化、序列化与反序列化等
- 线程安全问题,因为mybatis二级缓存是跨线程的,所以得考虑线程安全问题
- 清理策略,mybatis二级缓存作用域更大,使用范围广,意味着数据会更多,所以需要清理策略,否则会溢出。
- 其他 ……
如果是你来设计,你会怎么设计呢?大家自由发挥,我们来看看mybatis的设计,
首先,有一个Cache接口,接口源文件:Cache接口
Cache接口里面定义了一些缓存常见的方法,比如,增加一个缓存、移除一个缓存、清理缓存等等。
既然有接口,那肯定有实现类,借助ide工具,我们来看看有哪些实现类?也可以直接在GitHub上看,Cache接口实现类
还挺多,有的能见名知意,有的单词不认识、简写不认识,就不知道啥意思了,哈哈。挑几个单词认识的实现类:
- LoggingCache,估计就是和日志有关,但肯定不是普通的日志,普通的日志不会实现Cache接口。其实这是统计缓存命中率的日志。不信我们可以看LoggingCache对getObject方法的实现:
@Override
public Object getObject(Object key) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
hits++;
}
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
对命中率进行了计算,并且debug出来了
-
SynchronizedCache,肯定是同步了,果然,SynchronizedCache中的很多实现方法都加了synchronized关键字
-
TransactionalCache 肯定就是和事务相关了,还有其他一些,比如:
- LruCache:缓存清理策略,lru(least recently used)最近最少使用的被清理,这是mybatis二级缓存的默认清理策略
- FifoCache:缓存清理策略,FIFO (first in, first out),
- PerpetualCache:存储缓存的实现类,内部使用HashMap存储
- 其他等等……
看到这里,不知道大家有没有疑惑。你看哈,比如我要获取缓存,那我肯定需要记录缓存命中率、在事务中操作,也就是说,我的某个操作,仅仅用某个实现类是不行的,用LoggingCache没法控制事物,用TransactionalCache没法记录命中率,我两个同时用吧,两个实现类对同一个方法有不同的实现,拿到的肯定就是两个缓存,这……
说到这里,咱们就不得不回过头来说一下,责任链中的这个“链”字,在我们举的例子中,链中的节点是或的关系,这个节点不能审批,那我就交给下一个节点,下一个节点不行,再交给下一个……
但是也没谁规定必须是或的关系是吧,就算规定了,那我也可以不遵守,按照自己的想法写代码,又不违法。其实mybatis这里,一个链中每个节点之间的关系就是与,什么意思呢?就是把这些实现类LoggingCache、SynchronizedCache、TransactionalCache 串起来,LoggingCache先记录一下命中率,然后把后面的工作交给另外的实现类,如TransactionalCache 来进行事务相关操作,然后在交给xxx实现类做清理工作……像工厂流水线一样,走完一个一套流程,把所有的事情都做完。
比如LoggingCache
其他的Cache了实现类也是如此,如SynchronizedCache
其他的实现类也都是如此,那我们再打个断点,看看这个Cache链,是什么样子的吧!
就是如此,mybatis的Cache实现类像一个一个串起来,像个流水线,每个实现类完成自己的责任,然后交给下一个实现类,整个链走完,所有功能也都实现了!这其中还涉及到了装饰者模式。
回顾一些,mybatis二级缓存这里的责任链,好像和例子中的那个责任链不完全相同,比如:例子中,顶层抽象方法是抽象类,抽象类中还有一个引用指向下一个节点,但是在mybatis二级缓存中,顶层抽象方法变成了接口,指向下一个节点的引用声明在了实现类中,而且并不是所有的实现类都有(PerpetualCache就没有)……
文章开篇部分我也说过,设计模式只是提供了一个方向、思路,它并不是什么公式,可以直接套用,而是需要根据实际场景,灵活变通。使得代码能够满足需求,还尽可能的方便拓展、低耦合……(七大原则)