今天主要和大家分享的是责任链设计模式,说起责任链设计模式,我相信大家平时肯定是用过的,比如说 Java Web 开发中的 Filter 过滤器。今天我们还是按照惯例,先来看一个例子,一步步由浅入深进入责任链设计模式。
相信大家都玩过关卡游戏,在这类关卡游戏中,只有当你通过第一关才能进入第二关,通过第二关才能进入第三关。以此类推。
下面我将通过关卡游戏的小实例来进入责任链模式,在进入代码之前我们先来明确几点游戏要求。
关卡流程图
下面我们就来完成这个代码,首先定义 3 个关卡(类),代码非常简单,每个类的方法都一样,只是返回的结果不相同。
第一关:返回游戏得分 80
/**
*第一关
*/
public class FirstPassHandler {
public int handler(){
System.out.println("第一关-->FirstPassHandler");
return 80;
}
}
第二关:返回游戏得分 90
/**
*第二关
*/
public class SecondPassHandler {
public int handler(){
System.out.println("第二关-->SecondPassHandler");
return 90;
}
}
第三关:返回游戏得分 95
/**
*第三关
*/
public class ThirdPassHandler {
public int handler(){
System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
return 95;
}
}
客户端
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
int firstScore = firstPassHandler.handler();
//第一关的分数大于等于80则进入第二关
if(firstScore >= 80){
int secondScore = secondPassHandler.handler();
//第二关的分数大于等于90则进入第二关
if(secondScore >= 90){
thirdPassHandler.handler();
}
}
}
}
客户端代码也是非常简单,代码执行也没有任何问题。但是这段代码扩展性非常不好,有很大的问题。问题主要如下:
1. 假如现在要增加一个关卡,那么需要在 if 嵌套中增加一个 if 分支。如果关卡变多了,代码结构就会变成下面这样,if 嵌套将会一直循环下去,会非常糟糕的。
if(第1关通过){
// 第2关 游戏
if(第2关通过){
// 第3关 游戏
if(第3关通过){
// 第4关 游戏
if(第4关通过){
// 第5关 游戏
if(第5关通过){
// 第6关 游戏
if(第6关通过){
//...
}
}
}
}
}
}
2. 如果此时我想更改关卡的顺序,比如将第 3 关放到第 1 关,第 4 关放到第 2 关,每次更改关卡的顺序非常不便,而且更改关卡的顺序,对应关卡的逻辑也要跟着一起改变位置,非常麻烦,而且容易改出问题。
if(第1关通过(更改之前的第3关)){
// 第2关 游戏
if(第2关通过(更改之前的第4关)){
// 第3关 游戏
if(第3关通过(更改之前的第1关)){
// 第4关 游戏
if(第4关通过(更改之前的第2关)){
// 第5关 游戏
if(第5关通过){
// 第6关 游戏
if(第6关通过){
//...
}
}
}
}
}
}
那么,怎么解决上面的问题呢?答案就是我们今天要讲的责任链设计模式。责任链责任链,主要是体现在一个链字上面。也就是关卡与关卡之间将要形成一条链。但是这里有一个问题,这些关卡之间怎么样形成一条链呢?其实这样想也很简单,第一关通过需要进入第二关,那么是不是说第一关需要知道自己的下一关是第二关呢?同理,第二关需要知道自己的下一关是谁。用面向对象的思路来说,就是第一关要有一个属性(第二关)。在代码中的表现如下。
责任链中的第一关:
/**
*第一关
*/
public class FirstPassHandler {
/**
* 第一关的下一关是 第二关
*/
private SecondPassHandler secondPassHandler;
public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
this.secondPassHandler = secondPassHandler;
}
//本关卡游戏得分
private int play(){
return 80;
}
public int handler(){
System.out.println("第一关-->FirstPassHandler");
if(play() >= 80){
//分数>=80 并且存在下一关才进入下一关
if(this.secondPassHandler != null){
return this.secondPassHandler.handler();
}
}
return 80;
}
}
责任链中的第二关:
/**
*第二关
*/
public class SecondPassHandler {
/**
* 第二关的下一关是 第三关
*/
private ThirdPassHandler thirdPassHandler;
public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {
this.thirdPassHandler = thirdPassHandler;
}
//本关卡游戏得分
private int play(){
return 90;
}
public int handler(){
System.out.println("第二关-->SecondPassHandler");
if(play() >= 90){
//分数>=90 并且存在下一关才进入下一关
if(this.thirdPassHandler != null){
return this.thirdPassHandler.handler();
}
}
return 90;
}
}
责任链中的第三关:
/**
*第三关
*/
public class ThirdPassHandler {
//本关卡游戏得分
private int play(){
return 95;
}
/**
*
* 这是最后一关,因此没有下一关
*/
public int handler(){
System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
return play();
}
}
责任链中的客户端:
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
firstPassHandler.setSecondPassHandler(secondPassHandler);//第一关的下一关是第二关
secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二关的下一关是第三关
//说明:因为第三关是最后一关,因此没有下一关
//开始调用第一关 每一个关卡是否进入下一关卡 在每个关卡中判断
firstPassHandler.handler();
}
}
在上面的责任链中的客户端代码中,我们拿到第一个关卡,然后调用第一个关卡的 handler 方法,就可以让这一条链上的关卡都有机会被执行到。也就是说,在责任链设计模式中,我们只需要拿到链上的第一个处理者,那么链上的每个处理者都有机会处理相应的请求。
如果大家在之前学过链表的话理解起来就非常的简单。责任链设计模式和链表非常相似。
在上面的代码中,虽然我们将 3 个处理者(关卡)形成了一条链,但是代码扩展性非常不好,而且形成链很不方便。 首先,每个关卡中都有下一关的成员变量并且是不一样的,其次对应的 get、set 方法也不一样了,所以设置成为一条链的生活很不方便。
下面我们就想办法来解决上面 2 个问题,让每个关卡的下一关的引用是一样的。这个时候就需要一点抽象的思维了。在关卡之上抽象出来一个父类或者一个接口,然后每个具体的关卡继承或者实现,是不是就 OK 了的,答案是肯定的。接下来我们就来改造上面的三个关卡。
第一步:抽象出来一个抽象类
public abstract class AbstractHandler {
/**
* 下一关用当前抽象类来接收
*/
protected AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public abstract int handler();
}
第二步:每个关卡实现抽象类
1. 责任链中的第一关(改进之后)
/**
*第一关
*/
public class FirstPassHandler extends AbstractHandler{
private int play(){
return 80;
}
@Override
public int handler(){
System.out.println("第一关-->FirstPassHandler");
int score = play();
if(score >= 80){
//分数>=80 并且存在下一关才进入下一关
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
2. 责任链中的第二关(改进之后)
/**
*第二关
*/
public class SecondPassHandler extends AbstractHandler{
private int play(){
return 90;
}
public int handler(){
System.out.println("第二关-->SecondPassHandler");
int score = play();
if(score >= 90){
//分数>=90 并且存在下一关才进入下一关
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
3. 责任链中的第三关(改进之后)
/**
*第三关
*/
public class ThirdPassHandler extends AbstractHandler{
private int play(){
return 95;
}
public int handler(){
System.out.println("第三关-->ThirdPassHandler");
int score = play();
if(score >= 95){
//分数>=95 并且存在下一关才进入下一关
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
第三步:配置责任链
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
// 和上面没有更改的客户端代码相比,只有这里的set方法发生变化,其他都是一样的
firstPassHandler.setNext(secondPassHandler);//第一关的下一关是第二关
secondPassHandler.setNext(thirdPassHandler);//第二关的下一关是第三关
//说明:因为第三关是最后一关,因此没有下一关
//从第一个关卡开始
firstPassHandler.handler();
}
}
类继承关系图如下:
改进之后的代码基本上概括了责任链设计模式的使用,但是上述客户端的代码其实也是很繁琐的,后面我会带着大家继续优化责任链设计模式,将它的方方面面争取讲透彻。下面我们趁热打铁来看几个责任链设计模式的概念。
总结一下上面的几个概念:
这个概念很抽象,估计大家看了也很难理解,其实不用想的那么复杂。相信大家都有做过表单校验的工作,假如此时需要做一个登陆校验(用户名、密码、验证码),首先肯定是校验用户名,校验通过则进入下一步校验密码,否则提示用户,校验密码用过,则校验验证码,否则提示用户,表单全部校验通过,才开始提交到后台。其实这也是一个典型的责任链设计模式的运用。如下图:
就我个人理解而言,如果一个逻辑是按照一定的步骤进行的,而步骤之间存在复杂的逻辑计算,那么可以考虑使用责任链设计模式。或者说,当你的代码中出现这种情况的时候,你也可以考虑通过责任链设计模式来改进。
if(true){
//这里有很多的逻辑
return;
}
if(true){
//这里有很多的逻辑
return;
}
if(true){
//这里有很多的逻辑
return;
}
if(true){
//这里有很多的逻辑
return;
}
1. 抽象处理者(Handler)角色
定义出一个处理请求的接口(或者抽象类)。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个 Java 抽象类或者 Java 接口实现。下图中 AbstractHandler 类的聚合关系给出了具体子类对下家的引用,抽象方法 handleRequest() 规范了子类处理请求的操作。
public abstract class AbstractHandler {
protected AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public abstract void handlerRequest();
}
2. 具体处理者(ConcreteHandler)角色
具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下一个处理者。
/**
* 具体处理者A
*/
public class ConcreteHandler_A extends AbstractHandler{
public void handlerRequest() {
if(this.next != null){
this.next.handlerRequest();
}
}
}
/**
* 具体处理者B
*/
public class ConcreteHandler_B extends AbstractHandler{
public void handlerRequest() {
if(this.next != null){
this.next.handlerRequest();
}
}
}
3. 责任链客户端
责任链客户端设置处理者链,并且返回第一个处理者:
public class HandlerClient {
public static void main(String[] args) {
AbstractHandler firstHandler = new HandlerClient().getFirstHandler();
// 调用第一个处理者的handler方法
firstHandler.handlerRequest();
}
/**
* 设置责任链 并返回第一个处理者
* @return
*/
public AbstractHandler getFirstHandler(){
AbstractHandler a = new ConcreteHandler_A();
AbstractHandler b = new ConcreteHandler_B();
a.setNext(b);
return a;
}
}
在上述类结构图中,最上层是一个抽象类,抽象类持有自己的引用,其实是用来接收下一个处理者的。当然,大家也可以在抽象类的上层定义一个接口,这样扩展性在一定场景下会更优。
上面都是责任链设计模式的基本概念和一些简单的运用,接下来我将带领大家进入实战部分,让大家彻底的吃透责任链设计模式。
如果现在让你设计一个网关,你会怎么设计呢?在一般的网关中,都会经过 API 接口限流、黑名单拦截、用户会话、参数过滤等这么几个关键点。下面我们就通过责任链设计模式来实现网关权限控制。
首先定义一个抽象处理者,并持有对自己的引用(用于接收下一个处理者:
/**
* 网关抽象处理者
*/
public abstract class GetewayHandler {
protected GatewayHandler next;
public void setNext(GatewayHandler next) {
this.next = next;
}
public abstract void service();
}
定义具体处理者:API 接口限流
/**
* api接口限流
*/
public class ApiLimitGetewayHandler extends GetewayHandler{
public void service() {
System.out.println("第一步,api接口限流校验");
if(this.next != null){
this.next.service();
}
}
}
定义具体处理者:黑名单拦截
/**
* 黑名单拦截
*/
public class BlacklistGetwayHandler extends GatewayHandler{
public void service() {
System.out.println("第二步,黑名单拦截校验");
if(this.next != null){
this.next.service();
}
}
}
定义具体处理者:用户会话拦截
/**
* 用户会话拦截
*/
public class SessionGetwayHandler extends GetewayHandler{
public void service() {
System.out.println("第三步,用户会话拦截校验");
if(this.next != null){
this.next.service();
}
}
}
定义具体处理者:参数果过滤拦截
/**
* 参数过滤拦截
*/
public class ParamGetwayHandler extends GetewayHandler{
public void service() {
System.out.println("第四步,参数过滤拦截");
if(this.next != null){
this.next.service();
}
}
}
定义网关客户端:设置网关请求链
public class GetewayClient {
public static void main(String[] args) {
//api接口限流
GetewayHandler apiLimitGetewayHandler = new ApiLimitGetewayHandler();
//黑名单拦截
GetewayHandler blacklistGetwayHandler = new BlacklistGetwayHandler();
//用户会话拦截
GetewayHandler sessionGetwayHandler = new SessionGetwayHandler();
//参数过滤
GetewayHandler paramGetwayHandler = new ParamGetwayHandler();
apiLimitGetewayHandler.setNext(blacklistGetwayHandler);//api接口限流的下一步是黑名单拦截
blacklistGetwayHandler.setNext(sessionGetwayHandler);//杯名单拦截的下一步是用户会话拦截
sessionGetwayHandler.setNext(paramGetwayHandler);//用户会话拦截的下一步是参数果过滤拦截
apiLimitGetewayHandler.service();
}
}
运行结果如下:
在上面的网关客户端中,对责任链进行了初始化设置,实际上对于客户端而言,并不需要和如此复杂的设置链交互。对于客户端,只要拿到链上的第一个处理者就可以了。下面我们就结合工厂设计模式来实现责任链,简化客户端的交互。
首先我们来定义一个工厂类,返回第一个请求处理者:
/**
* 网关责任链工厂 设置请求链
*/
public class GetewayHandlerFactory {
public static GetewayHandler getFirstGetewayHandler(){
//api接口限流
GetewayHandler apiLimitGetewayHandler = new ApiLimitGetewayHandler();
//黑名单拦截
GetewayHandler blacklistGetwayHandler = new BlacklistGetwayHandler();
//用户会话拦截
GetewayHandler sessionGetwayHandler = new SessionGetwayHandler();
//参数过滤
GetewayHandler paramGetwayHandler = new ParamGetwayHandler();
apiLimitGetewayHandler.setNext(blacklistGetwayHandler);//api接口限流的下一步是黑名单拦截
blacklistGetwayHandler.setNext(sessionGetwayHandler);//杯名单拦截的下一步是用户会话拦截
sessionGetwayHandler.setNext(paramGetwayHandler);//用户会话拦截的下一步是参数果过滤拦截
return apiLimitGetewayHandler;
}
}
接下来,客户端通过工厂获取到第一个请求处理者:
public class GetewayClient {
public static void main(String[] args) {
GetewayHandler firstGetewayHandler = GetewayHandlerFactory.getFirstGetewayHandler();
firstGetewayHandler.service();
}
}
对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。我将使用枚举来教会大家怎么动态的配置请求链并且将每个请求者形成一条调用链。
首先定义一个枚举,枚举通过 GetewayEntity 来存储配置项,枚举如下:
public enum GetewayEnum {
/**
* 在这里大家需要注意到一个点 api接口限流是第一个处理者 因此没有 prehandlerId,也就是 它的prehandlerId = null
*/
API_HANDLER(new GetewayEntity(1,"api接口限流","com.simple.handler.demo003.ApiLimitGetewayHandler",null,2)),
BLACKLIST_HANDLER(new GetewayEntity(2,"黑名单拦截","com.simple.handler.demo003.BlacklistGetwayHandler",1,3)),
SESSION_HANDLER(new GetewayEntity(3,"用户会话拦截","com.simple.handler.demo003.SessionGetwayHandler",2,4)),
/**
* 这是最后一个处理者,因此没有下一个 nexthandlerId 也就是它的 nexthandlerId = null
*/
PARAM_HANDLER(new GetewayEntity(4,"参数过滤拦截","com.simple.handler.demo003.ParamGetwayHandler",3,null)),
;
GetewayEntity getewayEntity;
public GetewayEntity getGetewayEntity() {
return getewayEntity;
}
GetewayEnum(GetewayEntity getewayEntity) {
this.getewayEntity = getewayEntity;
}
}
大家不要把这个枚举想的那么复杂,其实就相当于数据库中的几条记录。下面我们通过一个 dao 操作来模拟从数据库获取配置信息,其实是从刚才定义的枚举中获取值。
1. 定义一个 Dao
public interface GetewayDao {
/**
* 根据 handlerId 获取配置项
* @param handlerId
* @return
*/
GetewayEntity getGetewayEntity(Integer handlerId);
/**
* 获取第一个处理者
* @return
*/
GetewayEntity getFirstGetewayEntity();
}
2. 定义 Dao 的实现类
public class GetewayDaoImpl implements GetewayDao{
/**
* 初始化,将枚举中配置的handler 初始化到map中,方便获取
*/
private static Map<Integer, GetewayEntity> getewayEntityHashMap = new HashMap<Integer, GetewayEntity>();
static {
GetewayEnum[] values = GetewayEnum.values();
for (GetewayEnum value : values) {
GetewayEntity getewayEntity = value.getGetewayEntity();
getewayEntityHashMap.put(getewayEntity.getHandlerId(),getewayEntity);
}
}
@Override
public GetewayEntity getGetewayEntity(Integer handlerId) {
return getewayEntityHashMap.get(handlerId);
}
/**
* 获取第一个handler的配置项 枚举
* @return
*/
@Override
public GetewayEntity getFirstGetewayEntity(){
for(Map.Entry<Integer,GetewayEntity> entry : getewayEntityHashMap.entrySet()){
GetewayEntity value = entry.getValue();
// 没有上一个handler的就是第一个
if(value.getPrehandlerId() == null){
return value;
}
}
return null;
}
}
3. 接下来改造责任链工厂的实现
/**
* 网关责任链工厂 设置请求链
*/
public class GetewayHandlerEnumFactory {
private static GetewayDao getewayDao = new GetewayDaoImpl();
public static GetewayHandler getFirstGetewayHandler(){
// 1\. 获取第一处理者 那么哪个是第一个处理者呢
// prehandlerId == null 的就是第一个handler
GetewayEntity firstGetewayEntity = getewayDao.getFirstGetewayEntity();
GetewayHandler firstGetewayHandler = newGetewayHandler(firstGetewayEntity);
if(firstGetewayHandler == null){
return null;
}
GetewayEntity tempGetewayEntity = firstGetewayEntity;
Integer nexthandlerId = null;//表示下一个处理者的id 唯一标识
GetewayHandler tempGetewayHandler = firstGetewayHandler;//临时变量 用于做递归设置下一个handler
//递归获取到tempGetewayHandler的下一个handlerId
while((nexthandlerId = tempGetewayEntity.getNexthandlerId()) != null){
GetewayEntity getewayEntity = getewayDao.getGetewayEntity(nexthandlerId);
GetewayHandler getewayHandler = newGetewayHandler(getewayEntity);
tempGetewayHandler.setNext(getewayHandler);
tempGetewayHandler = getewayHandler;
tempGetewayEntity = getewayEntity;
}
return firstGetewayHandler;
}
/**
* 反射实体化具体的处理者
* @param getewayEntity
* @return
*/
public static GetewayHandler newGetewayHandler(GetewayEntity getewayEntity){
String handlerClassName = getewayEntity.getHandlerClassName();
try {
Class<?> clazz = Class.forName(handlerClassName);
return (GetewayHandler) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
在上面工厂代码中,设置责任链稍微有点复杂,如果大家觉得比较吃力,建议大家稍微看一看递归调用和单项链表这个数据结构,然后再回过头来看看这段代码就不会那么吃力了。
4. 客户端调用工厂获取到第一个 handler
public class GetewayClient {
public static void main(String[] args) {
GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();
firstGetewayHandler.service();
}
}
5. 接下来通过 Debug 看一下获取到的第一个 handler 的结构。
从上面这张图中,可以很清晰的看到整个请求链。
ApiLimitGetewayHandler → BlacklistGetwayHandler → SessionGetwayHandler → ParamGetwayHandler
在上面的工厂中,设置整个请求链相对复杂一些,下面我再给大家扩展一种通过方法递归的方式来设置请求链。
/**
* 网关责任链工厂 设置请求链
*/
public class GetewayHandlerEnumFactory {
private static GetewayDao getewayDao = new GetewayDaoImpl();
public static GetewayHandler getFirstGetewayHandler1(){
// 1\. 获取第一处理者 那么那个是第一个处理者呢 prehandlerId == null 的就是第一个handler
GetewayEntity firstGetewayEntity = getewayDao.getFirstGetewayEntity();
GetewayHandler firstGetewayHandler = newGetewayHandler(firstGetewayEntity);
if(firstGetewayHandler == null){
return null;
}
setNextGetewayHandler(firstGetewayEntity,firstGetewayHandler);
return firstGetewayHandler;
}
private static void setNextGetewayHandler(GetewayEntity getewayEntity,GetewayHandler getewayHandler){
if(getewayHandler != null && getewayEntity != null){
Integer nexthandlerId = getewayEntity.getNexthandlerId();
GetewayEntity nextGetewayEntity = getewayDao.getGetewayEntity(nexthandlerId);
GetewayHandler nextGetewayHandler = newGetewayHandler(getewayEntity);
getewayHandler.setNext(nextGetewayHandler);
//递归 设置
setNextGetewayHandler(nextGetewayEntity,nextGetewayHandler);
}
}
/**
* 反射实体化具体的处理者
* @param getewayEntity
* @return
*/
private static GetewayHandler newGetewayHandler(GetewayEntity getewayEntity){
if(getewayEntity == null){
return null;
}
String handlerClassName = getewayEntity.getHandlerClassName();
try {
Class<?> clazz = Class.forName(handlerClassName);
return (GetewayHandler) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
通过递归方法设置责任链 Debug 结果如下:
在上面截图中,我们调用的是 getFirstGetewayHandler1 方法,结果和通过 while 循环递归是一样的,但是这种方式相对看着更加简洁一些。在实际工作过程中,大家可用根据自己的一个实际情况进行选择使用哪种方法,我比较推荐大家使用方法递归的方式实现。
在这里再提醒一下大家,如果需要更改请求链的执行顺序,直接更改枚举中的配置项,也就是 preHandler 或者 nextHandlerId 就可以更改请求链的顺序。
这一部分我不在通过具体的方式带着大家实现,因为这一部分和上面的通过枚举配置设置项很相似,其实就是把通过枚举配置这种方式换到了通过数据库来实现配置,再从数据库中读取出来。下面我和大家讲一下具体的实现思路。
原网址: 访问
创建于: 2023-05-30 14:33:26
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论