最近认真学了一下设计模式的责任链模式,觉得把它用在项目的优化上,应该可以使代码变得更加优雅。

定义

​ 使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。将所有处理者形成一条链,在链中决定哪个对象能够处理请求,并返回结果,不能处理则继续向下传递请求。

组成

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

优点

将请求和处理分开,请求者不需要知道是谁处理的,处理者可以不用知道请求的全貌。

缺点

  • 性能问题,每个请求从链头遍历到链尾,如果链比较长则性能低下。
  • 调试问题,属于递归调用,调试不方便。

使用场景

​ 在我的药源精鉴项目中,像一些需要过期处理的东西,比如,订单未支付过期处理,发红包未领取自动退还,代金券未支付过期处理等。可以采用的一种方式就是在存入redis的时候,会通过key来区分是什么业务,比如订单的key为AA开头,红包的key为BB开头,代金券的key为CC开头。那么,如果监听到redis中有过期的键值对的话,程序会得到键值对的key,通过key来判断属于哪部分业务,然后进行相应的处理。传统做法为,将所有的处理业务都写在一个类,或者一个方法中,通过多个if判断执行不同的逻辑,比如,如果是AA开头,那么执行订单的过期处理逻辑等等。

这种做法虽然没有问题,但是会使处理逻辑耦合在一起,变得比较臃肿,复杂,难以维护。而大部分设计模式都是为了增加程序的可维护性以及可扩展性,或者说解耦。

我想了想,在这个场景中,就可以应用责任链模式,相当于我把每一个处理逻辑都看作是一个独立的处理者,然后将他们形成一个处理链,这样当一个请求进来之后,通过遍历这个处理链来处理请求。在遍历链时,通过判断,如果能处理该请求,则处理并返回,不再继续向下遍历,如果不能处理则继续向下遍历,交给下一个处理者。

具体代码实现

通过上面的描述,我们知道,所有的处理者都是独立的,并且每个处理者应该都具备相同的行为—-处理过期逻辑,接下来就可以用责任链模式那一套了,首先我通过一个抽象类,定义出抽象的处理者。

public abstract class AbstractRedisExpireHandle {

//key的前缀,用于区分是什么业务
private String prefix;

//构造,子类必须实现
public AbstractRedisExpireHandle(){}

/**
* 操作前缀属性的公开方法
* @return
*/
public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

//抽象的,所有具体处理者应该实现的处理逻辑
abstract void expireHandle(String redisKey);

}

接下来看具体的处理者,所有的具体处理者都要继承抽象处理者,具体处理者之间是完全独立的,解耦的。

/**
* 商品订单过期
*/
@Component
public class GoodsOrderExpireHandle extends AbstractRedisExpireHandle {

//设置该业务的前缀
private static final String PREFIX = "dd";

public GoodsOrderExpireHandle(){
setPrefix(PREFIX);
}

@Reference(version = "1.0.0")
private TaskService taskService;

//实现具体处理逻辑
@Override
void expireHandle(String redisKey) {
taskService.goodsOrderExpireHandle(redisKey);
}
}
/**
* 红包过期
*/
@Component
public class RedPacketExpireHandle extends AbstractRedisExpireHandle {

//设置业务前缀
private static final String PREFIX = "kb";

@Reference(version = "1.0.0")
private TaskService taskService;

public RedPacketExpireHandle(){
setPrefix(PREFIX);
}

//实现具体处理逻辑
@Override
void expireHandle(String redisKey) {
taskService.redPacketExpireHandle(redisKey);
}
}

所有的具体处理者定义完后,需要将他们组成一个处理链,并且进行工作,接下来看如何将所有的相关具体处理者组成一个处理链,因为项目是基于spring的,所以这里使用了spring提供的几个相关接口,并且在spring容器启动的时候完成处理链的创建。

@Component
public class ExecuteHandle implements ApplicationContextAware,InitializingBean {

//spring容器
private ApplicationContext context;

//具体处理者的集合
private List<AbstractRedisExpireHandle> expireHandles = new ArrayList<>();

//该方法会在容器启动的时候被调用
@Override
public void afterPropertiesSet() throws Exception {
//从容器中找到所有继承了抽象处理者的类,并加入到集合中,从而形成处理链
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,AbstractRedisExpireHandle.class);
for(int i=0;i<beanNames.length;i++){
expireHandles.add((AbstractRedisExpireHandle)context.getBean(beanNames[i]));
}
}

//该方法会将spring的当前容器传递进来
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}


//遍历处理链,通过前缀来判断,能否处理逻辑,如果不能则继续遍历
public void handle(String redisKey){
if(expireHandles.size() > 0){
for(AbstractRedisExpireHandle abstractRedisExpireHandle : expireHandles){
if(redisKey.startsWith(abstractRedisExpireHandle.getPrefix())){
abstractRedisExpireHandle.expireHandle(redisKey);
}
}
}
}

}

上述代码比较简单,该类实现了spring的两个接口,一个是ApplicationContextAware和InitializingBean。

ApplicationContextAware为容器感知接口,实现这个接口需要实现setApplicationContext方法,spring会将容器当做参数传递到这个方法中,我们就可以使用当前容器了。

InitializingBean接口为初始化Bean的接口,实现这个接口需要实现afterPropertiesSet方法,该方法会在spring实例化这个类的时候被调用,可以做一些初始化工作,我就是在这个方法中将所有的具体处理者形成处理链的。

最后,通过遍历整个处理链,来处理相关的逻辑,结合之前所写的文章,使用该处理链处理请求的代码也十分简单,只需要调用上面的handle方法即可。

@Component
public class RedisExpireListener implements MessageListener {

@Autowired
private ExecuteHandle executeHandle;

@Override
public void onMessage(Message message, byte[] bytes) {
byte[] body = message.getBody();
String redisKey = new String(body);
executeHandle.handle(redisKey);
}
}

这样做之后,我们将所有具体处理者都进行了解耦,互相之间毫无关系。那么后续如果在有新的处理逻辑需要加入进来的话,只需要继承抽象处理者,实现相关的处理逻辑,具体处理者就可以进行工作了,无需修改其他任何代码。