面向对象设计的设计模式(十五):责任链模式

定义

责任链模式(Chain of Responsibility Pattern):为请求创建了一个接收者对象的链,每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

适用场景

在处理某个请求的时候,解决策略因条件不同而不同。这时,相对于使用if-else来区分不同的条件和对应的解决策略,我们可以使用责任链模式,将不同条件和对应的解决策略封装到一个类中,即不同的处理者。然后将这些处理者组成责任链,在当前处理者无法处理或不符合当前条件时,将请求传递给下一个处理者。

现在我们清楚了责任链模式的适用场景,下面看一下责任链模式的成员和类图。

成员与类图

成员

责任链模式的结构比较简单,不包括客户端只有两个成员:

  • 处理者(Handler):处理者定义处理请求的接口
  • 具体处理者(Concrete Handler): 具体处理者实现处理者声明的接口,负责处理请求

模式类图

责任链模式类图

代码示例

场景概述

模拟一个 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

从日志的输出结果上看,我们重构后的责任链方案没有问题。

下面看一下上面代码对应的类图。

代码对应的类图

重构前:

责任链模式代码示例类图一

重构后:

责任链模式代码示例类图二

优点

  • 处理者之间的责任分离,处理者只要处理好自己的逻辑即可
  • 方便修改每个处理者的处理逻辑,也方便删除或者添加处理者,或者改变责任链中处理者的顺序。

缺点

  • 因为需要在责任链上传递责任,直到找到合适的对象来处理,所以可能会导致处理的延迟。因此在延迟不允许过高的场景下不适合使用责任链模式。

iOS SDK 和 JDK中的应用

  • iOS SDK中的响应者链就是责任链模式的实践:如果当前视图无法响应则传递给下一层级视图。
  • servlet中的Filter可以组成FilterChain,是责任链模式的一种实践。

原文发布于微信公众号 - 程序员维他命(J_Knight_)

原文发表时间:2019-05-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券