职场小白初涉设计模式之责任链模式
最近,接到一个需求,在数据计算之前,根据需求的具体内容对数据进行过滤,保留下需要计算的那部分。
对于职场小白没经验的我来说,拿到需求,在确定于什么位置过滤数据以后,就开始义无反顾地码代码!!
怎么码呢?当然是建立需要过滤的接口及实现类,然后在里面进行过滤的具体操作。
由于过滤数据的两部分是对同一内容进行操作,于是在计算数据之前,
嗯,调用第一个实现类方法得到要过滤的内容,循环过滤数据,过滤第一部分;
再来调用第二个实现类方法得到要过滤的内容,循环过滤数据,过滤第二部分。
到这里,需求的功能性基本上算是完成了。但这样的方式好么?!
如果再来第三个第四个相同的过滤操作呢?!接着如上循环么?!
明眼人都能看出来这种方式存在了很大的代码冗余,效率并不是很高,
且后一部分的过滤在前一部分的过滤之后,耦合太紧,以后再看到的话肯定头疼。
于是,在同事亦师父的林哥的耐心指导下,开始以责任链的形式来优化这部分代码。
一、对责任链的理解
责任链模式(Chain of Responsibility Pattern)即为请求创建一个接收者对象的链,给与请求类型,将请求发送者与接收者之间进行解耦。请求的处理被每一个接收者进行接收并处理,如果能处理则按照接收者的既定规则处理,然后在该责任链上层层传递。也可以在每一个接收者中持有下一个接收者的引用,如果自己不能处理,则传给下一个进行处理。
请求由责任链上的每个接收者进行处理,因此,客户在传入请求之后,不需要关心接收者的处理细节以及如何传递的,这就实现了请求的发送者和请求接收者之间进行了解耦。
(一)大致实现
1、首先创建一个Handler类,其包含对请求进行处理的方法HandlerRequest();
2、对于不同的请求处理方式,创建其相应的接收者类,并继承并重写Handler父类中的HandlerRequest()方法,以自己的方式进行实现;
3、对于接收者类的传递上,是在每个接收者类中持有下一接收者的引用,这样处理不了的请求就传递给下一个接收者进行处理。
(二)优缺点
优点:1、首先是责任的分担。每个类都只需承担自己的责任进行处理,如果不能处理就传递给下一个对象,明确各类的职责范围,符合类的最小封装原则。
2、可以根据实际场景的需要自由组合处理流程。如需变更,只需要重新分配该对象链即可。
3、类与类之间以松耦合的方式来传递请求,降低了耦合。
缺点: 由于请求在各个接收者之间进行传递,而每个接收者处理的方式不同,可能会影响处理速度。
(三)应用场景
责任链既然叫链,即是用于链式处理的场景中,使得工作流程化、处理过程流程化。
例如:1、Web开发过程中对请求的各种filter处理,如请求处理和过滤、对Encoding的处理、获取客户端IP地址、获取客户证书等。(这个是大家最熟悉的部分)
2、JS的冒泡事件。(这个本人没太过深入研究,看了下,大致意思是某事件被触发,如鼠标点击了一下按钮,那么该事件会在该元素的所有祖先当中被触发,即自下而上的。)
3、多个对象处理一个请求时。
二、我是如何来实现责任链的
说完了个人对责任链的理解,接下来就通过一个简单的例子来讲解本人是如何实现责任链的。
1、定义一个IRule接口,制定请求处理方法的规则,各接收者都将实现该接口并重写该方法;
1 public interface IRule { 2 3 /** 4 * 对字符串进行处理 5 * @return 6 */ 7 Object handler(); 8 }
2、定义一个RuleChain类,主要包含两部分,一部分是能接收传递进来的请求对象,一部分是对请求进行处理。
这两个部分保证了在传递请求继续不同处理时,能将接收者加入到责任链中形成链式结构,后续在增加新的相关接收处理者对象时只需实现自身处理并加入到责任链中即可;同时每一个接收者都重写了该责任链的处理方法,这样在进行责任链处理时,如果是对相同数据进行处理,只需要一个循环来进行验证即可,避免了代码的冗余。
1 public class RuleChain { 2 3 /** 4 * 装载各个接收者对象的链 5 */ 6 private List<IRule> chain; 7 8 public RuleChain() { 9 chain = new ArrayList<>(); 10 } 11 12 public RuleChain addRule(IRule rule) { 13 chain.add(rule); 14 return this; 15 } 16 17 /** 18 * 各个接收者自行处理数据 19 */ 20 public void handler(StringObj stringObj){ 21 for (IRule rule : chain){ 22 stringObj = rule.handler(); 23 } 24 } 25 }
3、接收者的实现方法
这部分主要是实现RuleChain接口并根据自身情况重写其请求处理方法。这样RuleChain可以统一地进行请求处理,然后轮到该接收者时,就会调用其重写的请求处理方法进行处理。
1 public class ConcreteHandlerA implements IRule { 2 3 private StringObj str; 4 5 public ConcreteHandlerA(StringObj str){ 6 this.str = str; 7 } 8 9 /** 10 * 该方法在字符串上上添加“ConcreteHandlerA” 11 */ 12 @Override 13 public StringObj handler() { 14 String string=str.getStr()+"ConcreteHandlerA接收者开始处理,"; 15 str.setStr(string); 16 return str; 17 } 18 }
1 public class ConcreteHandlerB implements IRule{ 2 3 private StringObj str; 4 5 public ConcreteHandlerB(StringObj str){ 6 this.str = str; 7 } 8 9 /** 10 * 判断前面字符串的长度, 11 * @return 12 */ 13 @Override 14 public StringObj handler() { 15 String end = "ConcreteHandlerB处理完毕,责任链结束"; 16 String string = ""; 17 if (str.getStr().length() > 10) { 18 string=str.getStr()+ "前面字符串长度为"+str.getStr().length()+","+end; 19 }else{ 20 string=str.getStr()+ "前面字符串长度不足10,只有"+str.getStr().length()+","+end; 21 } 22 str.setStr(string); 23 return str; 24 } 25 }
4、Main方法验证实现及输出结果
1 public class Main { 2 3 public static void main(String[] args) { 4 StringObj stringObj = new StringObj("责任链开始,"); 5 RuleChain chain = new RuleChain().addRule(new ConcreteHandlerA(stringObj)).addRule(new ConcreteHandlerB(stringObj)); 6 chain.handler(stringObj); 7 System.out.println(stringObj.getStr()); 8 } 9 } 10 11 ================================================== 12 责任链开始,ConcreteHandlerA接收者开始处理,前面字符串长度为30,ConcreteHandlerB处理完毕,责任链结束
三、责任链实现的设计模式六大原则体现
如上所示则为一个简答的责任链的大致实现过程。
1、通过上面的例子我们可以看到,每一个类只进行自己的那部分处理方式,符合设计模式的单一职责原则,实现了高内聚低耦合的方针;
2、在Main函数中的main方法中,对于RuleChain来说,我们只需在后续增加各个处理模块即可,然后在每个处理模块中实现自定义处理。在对各个处理模块的增加来说,我们只需实现IRule接口,并重写其handler方法,即可满足需求。这符合设计模式的开闭原则,即对扩展开放,对修改关闭。
3、接收者的各个类都统一实现了专门的IRule接口,而不会去实现不必要的其他接口,符合接口隔离原则;
4、接收者的handler方法都实现了IRule中的方法,IRule负责抽象方法,而具体的实现细节都放在了各个接收者内部进行处理,符合依赖倒置原则;
5、我们从上述例子中可以看到,接收者的类与类之间是不存在耦合的,其链接都是依靠RuleChain来进行的,因此符合迪米特法则,类与类之间的耦合性降低。
6、里氏替换原则在这里暂时不是很明显。
四、其他
大致先说到这里,后续再进行补充。
如有描述不足之处,请指教!