java设计模式之责任链模式,观摩mybatis二级缓存设计思路

前言

软件设计模式,其实就是指在某种场景下,一套符合要求、具有良好拓展性的代码设计。接口、类之间怎样组合、怎样关联,使得代码即满足需求,又耦合性低。当然了,由于具体的场景不尽相同,所以各种设计模式也只是一个提供一个思路、概要,具体细节需要根据具体的场景变通。

java语言,由于其悠久的历史和庞大良好的生态,使用人数众多,前人总结出了23中设计模式。这23中设计模式可分为3中类型:

  • 创建型:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
  • 结构性:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享
    元模式、代理模式
  • 行为型:模版方法模式、命令模式、访问者模式、迭代器模式、观察者
    模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模
    式、策略模式、职责链模式(责任链模式)

前情引入

今天咱们要说的是 责任链模式,咱们先引入一个具体的场景:

学校OA系统的采购审批项目:需求是

  1. 采购员采购教学器材
  2. 如果金额 小于等于5000, 由系主任审批 (0<=x<=5000)
  3. 如果金额 小于等于10000, 由院长审批 (5000<x<=10000)
  4. 如果金额 小于等于30000, 由副校长审批 (10000<x<=30000)
  5. 如果金额 超过30000以上,有校长审批 ( 30000<x)

咱们该怎么设计呢?

不使用设计模式

首先分析,可能存在那些类?按照我目前的水平,我首先想到,有四个类,因为有系主任、院长、副校长、校长四个角色,然后我还能想到,由于这四个角色都具有审批功能,所以还应该有一个接口Approvable,接口中存在一个approve方法,系主任、院长、副校长、校长四个角色都实现这个接口。能想到的就这么多。另外还需要个请求类发起请求,具体代码如下:

  1. Approvable

public  interface Approvable {

    /** 抽象的审批方法,由具体的信息审批者实现 */
    public abstract void approve(PurchaseRequest request);

}
  1. 系主任
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));
    }
}

  1. 院长
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));
    }
}
  1. 副校长
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));
    }
}
  1. 校长
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));
    }
}
  1. 请求类
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);
        }
    }
}

当然,这样设计能满足需求,但是这样做有什么问题呢?

  1. 假设现在副校长的审批权力调整,10000到50000都可以由副校长审批。那么,我们不仅需要改动副校长中的审批代码,还需要修改客户端中的if……else If……的判断逻辑。
  2. 假设,现在需要增加一个审批人,指导员,那么还是需要到客户端中修改if……else if的判断逻辑。

所以现在存在的问题就是:客户端必须完全知晓,存在的所有审批级别以及每个审批级别的审批金额,这是很严重的耦合关系,不利于系统的扩展。那如果使用责任链设计模式,又是如何设计呢?

使用设计模式

使用责任链设计模式,首先,和之前分析的一样,需要存在四个角色,也就是四个类,而且这个四个类有共同的方法,所以也需要实现同一个抽象方法,但是这里不再是声明成接口,而是声明成了抽象类,为什么要这么做,这是因为,需要在抽象类中定义一个变量,这个变量引用下一个审批者,如果当前审批者无法审批此次金额,就将任务交给下一个审批者。代码:

  1. 审批者抽象类
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;
    }
}

  1. 系主任
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);
        }
    }
}
  1. 院长
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);
        }
    }
}
  1. 副校长
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);
        }
    }
}
  1. 校长
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);
        }
    }
}
  1. 请求类
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接口
Cache接口里面定义了一些缓存常见的方法,比如,增加一个缓存、移除一个缓存、清理缓存等等。

既然有接口,那肯定有实现类,借助ide工具,我们来看看有哪些实现类?也可以直接在GitHub上看,Cache接口实现类
Cache接口实现类
还挺多,有的能见名知意,有的单词不认识、简写不认识,就不知道啥意思了,哈哈。挑几个单词认识的实现类:

  1. 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出来了

  1. SynchronizedCache,肯定是同步了,果然,SynchronizedCache中的很多实现方法都加了synchronized关键字
    SynchronizedCache

  2. 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二级缓存链
就是如此,mybatis的Cache实现类像一个一个串起来,像个流水线,每个实现类完成自己的责任,然后交给下一个实现类,整个链走完,所有功能也都实现了!这其中还涉及到了装饰者模式。

回顾一些,mybatis二级缓存这里的责任链,好像和例子中的那个责任链不完全相同,比如:例子中,顶层抽象方法是抽象类,抽象类中还有一个引用指向下一个节点,但是在mybatis二级缓存中,顶层抽象方法变成了接口,指向下一个节点的引用声明在了实现类中,而且并不是所有的实现类都有(PerpetualCache就没有)……

文章开篇部分我也说过,设计模式只是提供了一个方向、思路,它并不是什么公式,可以直接套用,而是需要根据实际场景,灵活变通。使得代码能够满足需求,还尽可能的方便拓展、低耦合……(七大原则)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值