责任链模式(Chain of Responsibility Pattern):为请求创建了一个接收者对象的链,每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
在处理某个请求的时候,解决策略因条件不同而不同。这时,相对于使用if-else
来区分不同的条件和对应的解决策略,我们可以使用责任链模式,将不同条件和对应的解决策略封装到一个类中,即不同的处理者。然后将这些处理者组成责任链,在当前处理者无法处理或不符合当前条件时,将请求传递给下一个处理者。
现在我们清楚了责任链模式的适用场景,下面看一下责任链模式的成员和类图。
责任链模式的结构比较简单,不包括客户端只有两个成员:
责任链模式类图
模拟一个 ATM 取现金的场景:ATM机器有50,20,10面值的纸币,根据用户需要提取的现金金额来输出纸币张数最少的等价金额的纸币。
比如用户需要取130元,则ATM需要输出2张50面额的纸币,1张20面额的纸币,1张10面额的纸币;而不是6张20面额的纸币加1张10面额的纸币。
显然,为了输出最少张数的纸币,ATM在计算的时候是从面额最大的纸币开始计算的。
如果不使用责任链模式,我们可能会写一个do-while
循环,在循环里面再根据纸币的面额在做if-else
判断,不断去尝试直到将面额除尽(没有余数)。但是如果未来面额的数值发生变化,或者添加新的面额的纸币的话,我们还需要更改判断条件或增加if-else
语句,这显然违反了开闭原则。
但是如果使用责任链模式,我们将每个面值的纸币当做责任链中的一个处理者(节点,node),自成一类,单独做处理。然后将这些处理者按照顺序连接起来(50,20,10),按照顺序对用户输入的数值进行处理即可。
这样做的好处是,如果以后修改面值或添加一种新的面值,我们只需要修改其中某一个处理者或者新建一个处理者类,再重新插入到责任链的合适的位置即可。
下面我们看一下如何用代码来模拟该场景。
首先创建抽象处理者DispenseChainNode
:
//================== DispenseChainNode.h ==================
@interface DispenseChainNode : NSObject <DispenseProtocol>
{
@protected DispenseChainNode *_nextChainUnit;
}
- (void)setNextChainUnit:(DispenseChainNode *)chainUnit;
@end
//================== DispenseChainNode.m ==================
@implementation DispenseChainNode
- (void)setNextChainNode:(DispenseChainNode *)chainNode{
_nextChainNode = chainNode;
}
- (void)dispense:(int)amount{
return;
}
@end
DispenseChainNode
是责任链节点,也就是具体处理者的父类,它持有DispenseChainNode
的实例,用来保存当前节点的下一个节点。这个下一个节点的实例是通过setNextChainNode:
方法注入进来的
而且,DispenseChainNode
遵循协议,这个协议只有一个方法,就是dispense:
方法,每个节点都实现这个方法来对输入的金额做处理。(dispense 单词的意思是分配,分发)现在我们根据需求,创建具体处理者,也就是针对50,20,10面额的具体处理者:
50面额的具体处理者:
//================== DispenseChainNodeFor50Yuan.h ==================
@interface DispenseChainNodeFor50Yuan : DispenseChainNode
@end
//================== DispenseChainNodeFor50Yuan.m ==================
@implementation DispenseChainNodeFor50Yuan
- (void)dispense:(int)amount{
int unit = 50;
if (amount >= unit) {
int count = amount/unit;
int remainder = amount % unit;
NSLog(@"Dispensing %d of %d",count,unit);
if (remainder != 0) {
[_nextChainNode dispense:remainder];
}
}else{
[_nextChainNode dispense:amount];
}
}
@end
20面额的具体处理者:
//================== DispenseChainNodeFor20Yuan.h ==================
@interface DispenseChainNodeFor20Yuan : DispenseChainNode
@end
//================== DispenseChainNodeFor20Yuan.m ==================
@implementation DispenseChainNodeFor20Yuan
- (void)dispense:(int)amount{
int unit = 20;
if (amount >= unit) {
int count = amount/unit;
int remainder = amount % unit;
NSLog(@"Dispensing %d of %d",count,unit);
if (remainder != 0) {
[_nextChainNode dispense:remainder];
}
}else{
[_nextChainNode dispense:amount];
}
}
@end
10面额的具体处理者:
//================== DispenseChainNodeFor10Yuan.h ==================
@interface DispenseChainNodeFor10Yuan : DispenseChainNode
@end
//================== DispenseChainNodeFor10Yuan.m ==================
@implementation DispenseChainNodeFor10Yuan
- (void)dispense:(int)amount{
int unit = 10;
if (amount >= unit) {
int count = amount/unit;
int remainder = amount % unit;
NSLog(@"Dispensing %d of %d",count,unit);
if (remainder != 0) {
[_nextChainNode dispense:remainder];
}
}else{
[_nextChainNode dispense:amount];
}
}
@end
上面三个具体处理者在dispense:
方法的处理都是类似的:
首先查看当前值是否大于面额
现在我们创建好了三个具体处理者,我们再创建一个ATM类来把这些节点串起来:
//================== ATMDispenseChain.h ==================
@interface ATMDispenseChain : NSObject<DispenseProtocol>
@end
//================== ATMDispenseChain.m ==================
@implementation ATMDispenseChain
{
DispenseChainNode *_chainNode;
}
- (instancetype)init{
self = [super init];
if(self){
DispenseChainNodeFor50Yuan *chainNode50 = [[DispenseChainNodeFor50Yuan alloc] init];
DispenseChainNodeFor20Yuan *chainNode20 = [[DispenseChainNodeFor20Yuan alloc] init];
DispenseChainNodeFor10Yuan *chainNode10 = [[DispenseChainNodeFor10Yuan alloc] init];
_chainNode = chainNode50;
[_chainNode setNextChainNode:chainNode20];
[chainNode20 setNextChainNode:chainNode10];
}
return self;
}
- (void)dispense:(int)amount{
NSLog(@"==================================");
NSLog(@"ATM start dispensing of amount:%d",amount);
if (amount %10 != 0) {
NSLog(@"Amount should be in multiple of 10");
return;
}
[_chainNode dispense:amount];
}
@end
ATMDispenseChain
这个类在初始化的时候就将三个具体处理者并按照50,20,10的顺序连接起来,并持有一个DispenseChainNode
的指针指向当前的具体处理者(也就是责任链的第一个节点,面额50的具体处理者,因为面额的处理是从50开始的)。
OK,现在我们把三个具体处理者都封装好了,可以看一下如何使用:
ATMDispenseChain *atm = [[ATMDispenseChain alloc] init];
[atm dispense:230];
[atm dispense:70];
[atm dispense:40];
[atm dispense:10];
[atm dispense:8];
创建ATMDispenseChain
的实例后,分别传入一些数值来看一下处理的结果:
==================================
ATM start dispensing of amount:230
Dispensing 4 of 50
Dispensing 1 of 20
Dispensing 1 of 10
==================================
ATM start dispensing of amount:70
Dispensing 1 of 50
Dispensing 1 of 20
==================================
ATM start dispensing of amount:40
Dispensing 2 of 20
==================================
ATM start dispensing of amount:10
Dispensing 1 of 10
==================================
ATM start dispensing of amount:8
Amount should be in multiple of 10
从日志的输出可以看出,我们的责任链处理是没有问题的,针对每个不同的数值,ATMDispenseChain
实例都作出了最正确的结果。
需要注意的是,该代码示例中的责任链类(
ATMDispenseChain
)并没有在上述责任链模式的成员中。不过此处不必做过多纠结,我们在这里只是在业务上稍微多做一点处理罢了。其实也完全可以不封装这些节点,直接逐个调用setNextChainNode:
方法组装责任链,然后将任务交给第一个处理者即可。
需求完成了,是否可以做个重构?
我们回去看一下这三个具体处理者在dispense:
方法的处理是非常相似的,他们的区别只有处理的面额数值的不同:而我们其实是创建了针对这三个面值的类,并将面值(50,20,10)硬编码在了这三个类中。这样做是有缺点的,因为如果后面的面额大小变了,或者增加或者减少面额的话我们会修改这些类或添加删除这些类(即使这也比不使用责任链模式的if-else
要好一些)。
因此我们可以不创建这些与面额值硬编码的具体处理类,而是在初始化的时候直接将面额值注入到构造方法里面即可!这样一来,我们可以随意调整和修改面额了。下面我们做一下这个重构:
首先删除掉三个具体处理者DispenseChainNodeFor50Yuan
,DispenseChainNodeFor20Yuan
,DispenseChainNodeFor10Yuan
。
接着在DispenseChainNode
添加传入面额值的初始化方法以及面额值的成员变量:
//================== ADispenseChainNode.h ==================
@interface DispenseChainNode : NSObject <DispenseProtocol>
{
@protected DispenseChainNode *_nextChainNode;
@protected int _dispenseValue;
}
- (instancetype)initWithDispenseValue:(int)dispenseValue;
- (void)setNextChainNode:(DispenseChainNode *)chainNode;
@end
//================== ADispenseChainNode.m ==================
@implementation DispenseChainNode
- (instancetype)initWithDispenseValue:(int)dispenseValue
{
self = [super init];
if (self) {
_dispenseValue = dispenseValue;
}
return self;
}
- (void)setNextChainNode:(DispenseChainNode *)chainNode{
_nextChainNode = chainNode;
}
- (void)dispense:(int)amount{
if (amount >= _dispenseValue) {
int count = amount/_dispenseValue;
int remainder = amount % _dispenseValue;
NSLog(@"Dispensing %d of %d",count,_dispenseValue);
if (remainder != 0) {
[_nextChainNode dispense:remainder];
}
}else{
[_nextChainNode dispense:amount];
}
}
@end
我们给DispenseChainNode
添加了initWithDispenseValue:
方法后,就可以根据需求随意生成不同面额的具体处理者了。
接着我们思考一下之前的ATMDispenseChain
可以做哪些改变?
既然DispenseChainNode
可以根据不同的面额值生成处理不同面额的具体处理者实例,那么对于串联多个具体处理者的类ATMDispenseChain
是不是也可以添加一个注入面额数组的初始化方法呢?比如输入[50,20,10]
的数组就可以生成50,20,10面额的具体处理者了;而且数组是有序的,传入数组的元素顺序就可以是责任链中节点的顺序。
思路有了,我们看一下具体实现:
//================== ATMDispenseChain.m ==================
@implementation ATMDispenseChain
{
DispenseChainNode *_firstChainNode;
DispenseChainNode *_finalChainNode;
int _minimumValue;
}
- (instancetype)initWithDispenseNodeValues:(NSArray *)nodeValues{
self = [super init];
if(self){
NSUInteger length = [nodeValues count];
[nodeValues enumerateObjectsUsingBlock:^(NSNumber * nodeValue, NSUInteger idx, BOOL * _Nonnull stop) {
DispenseChainNode *iterNode = [[DispenseChainNode alloc] initWithDispenseValue:[nodeValue intValue]];
if (idx == length - 1 ) {
_minimumValue = [nodeValue intValue];
}
if (!self->_firstChainNode) {
//because this chain is empty, so the first node and the final node will refer the same node instance
self->_firstChainNode = iterNode;
self->_finalChainNode = self->_firstChainNode;
}else{
//appending the next node, and setting the new final node
[self->_finalChainNode setNextChainNode:iterNode];
self->_finalChainNode = iterNode;
}
}];
}
return self;
}
- (void)dispense:(int)amount{
NSLog(@"==================================");
NSLog(@"ATM start dispensing of amount:%d",amount);
if (amount % _minimumValue != 0) {
NSLog(@"Amount should be in multiple of %d",_minimumValue);
return;
}
[ _firstChainNode dispense:amount];
}
@end
重构后的ATMDispenseChain
类新增了initWithDispenseNodeValues:
方法,需要从外部传入面额值的数组。在这个方法里面根据传入的数组构造了整条责任链。
而在dispense:
方法里面则是从责任链的第一个节点来处理面额,并在方法最前面取最小面额的值来做边界处理。
OK,到现在处理者类和责任链类都创建好了,我们看一下如何使用:
NSArray *dispenseNodeValues = @[@(100),@(50),@(20),@(10)];
ATMDispenseChain *atm = [[ATMDispenseChain alloc] initWithDispenseNodeValues:dispenseNodeValues];
[atm dispense:230];
[atm dispense:70];
[atm dispense:40];
[atm dispense:10];
[atm dispense:8];
是不是感觉简洁多了?我们只需要传入一个面额值的数组即可构造出整条责任链并直接使用。来看一下日至输出:
==================================
ATM start dispensing of amount:230
Dispensing 2 of 100
Dispensing 1 of 20
Dispensing 1 of 10
==================================
ATM start dispensing of amount:70
Dispensing 1 of 50
Dispensing 1 of 20
==================================
ATM start dispensing of amount:40
Dispensing 2 of 20
==================================
ATM start dispensing of amount:10
Dispensing 1 of 10
==================================
ATM start dispensing of amount:8
Amount should be in multiple of 10
从日志的输出结果上看,我们重构后的责任链方案没有问题。
下面看一下上面代码对应的类图。
重构前:
责任链模式代码示例类图一
重构后:
责任链模式代码示例类图二
servlet
中的Filter
可以组成FilterChain
,是责任链模式的一种实践。