[关闭]
@qidiandasheng 2021-01-11T22:28:57.000000Z 字数 19782 阅读 1368

设计模式(五):行为型模式之观察者、模板、策略、责任链(😁)

架构


四种行为型模式

介绍

行为型模式为设计模式的一种类型,用来识别对象之间的常用交流模式并加以实现。如此可在进行这些交流活动时增强弹性。

后三种模式具有相同的作用:复用和扩展,在实际的项目开发中比较常用,特别是框架开发中,我们可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源码的情况下,基于扩展点定制化框架的功能。

工厂模式和策略模式的区别

工厂模式是解耦对象的创建和使用,策略模式解耦的是策略的定义、创建、使用这三部分。实际上策略模式也会用到简单工厂来创建不同的策略对象。

❤观察者模式

定义

观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都可以到通知并做相应针对性的处理。

适用场景

凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。通常我们使用观察者模式实现一个对象的改变会令其他一个或多个对象发生改变的需求,比如换肤功能,监听列表滚动的偏移量等等。

成员与类图

成员

观察者模式有四个成员:

模式类图

导出图片Mon Apr 13 2020 15_33_25 GMT+0800 (中国标准时间).png-197.9kB

代码示例

场景概述

模拟这样的一个场景:客户(投资者)订阅理财顾问的建议购买不同价格的股票。当价格信息变化时,所有客户会收到通知(可以使短信,邮件等等),随后客户查看最新数据并进行操作。

一个理财顾问可能服务于多个客户,而且消息需要及时传达到各个客户那边;而客户接收到这些消息后,需要对这些消息做出相应的措施。这种一对多的通知场景我们可以使用观察者模式:理财顾问是被观察的目标(Subject),而TA的客户则是观察者(Observer)。

代码实现

首先我们定义观察者Observer:

  1. //================== Observer.h ==================
  2. @interface Observer : NSObject
  3. {
  4. @protected Subject *_subject;
  5. }
  6. - (instancetype)initWithSubject:(Subject *)subject;
  7. - (void)update;
  8. @end
  9. //================== Observer.m ==================
  10. @implementation Observer
  11. - (instancetype)initWithSubject:(Subject *)subject{
  12. self = [super init];
  13. if (self) {
  14. _subject = subject;
  15. [_subject addObserver:self];
  16. }
  17. return self;
  18. }
  19. - (void)update{
  20. NSLog(@"implementation by subclasses");
  21. }

Observer类是具体观察者的父类,它声明了一个传入目标类(Subject)的构造方法并在构造方法里持有这个传入的实例。而且在这个构造方法里,调用了Subject的‘添加观察者’的方法,即addObserver:,目的是将当前的观察者实例放入Subject的用来保存观察者实例的集合中(具体操作可以在下面讲解Subject类的部分看到)

另外它也定义了update方法供子类使用。

下面我们看一下具体观察者类Investor:

  1. //================== Investor.h ==================
  2. @interface Investor : Observer
  3. @end
  4. //================== Investor.m ==================
  5. @implementation Investor
  6. - (void)update{
  7. float buyingPrice = [_subject getBuyingPrice];
  8. NSLog(@"investor %p buy stock of price:%.2lf",self,buyingPrice);
  9. }
  10. @end

具体观察者实现了该协议中定义的方法update方法,在这个方法里面,首先通过getBuyingPrice方法获得到最新的在监听的数据buyingPrice,然后再做其他操作。这里为了方便展示,直接使用日至打印出当前的具体观察者实例的内存地址和当前监听的最新值。

下面我们声明一下目标类和具体目标类:

目标类Subject

  1. //================== Subject.h ==================
  2. @interface Subject : NSObject
  3. {
  4. @protected float _buyingPrice;
  5. @protected NSMutableArray <Observer *>*_observers;
  6. }
  7. - (void)addObserver:(Observer *) observer;
  8. - (void)removeObserver:(Observer *) observer;
  9. - (void)notifyObservers;
  10. - (void)setBuyingPrice:(float)price;
  11. - (double)getBuyingPrice;
  12. @end
  13. //================== Subject.m ==================
  14. @implementation Subject
  15. - (instancetype)init{
  16. self = [super init];
  17. if (self) {
  18. _observers = [NSMutableArray array];
  19. }
  20. return self;
  21. }
  22. - (void)addObserver:( Observer * ) observer{
  23. [_observers addObject:observer];
  24. }
  25. - (void)removeObserver:( Observer *) observer{
  26. [_observers removeObject:observer];
  27. }
  28. - (void)notifyObservers{
  29. [_observers enumerateObjectsUsingBlock:^(Observer * _Nonnull observer, NSUInteger idx, BOOL * _Nonnull stop) {
  30. [observer update];
  31. }];
  32. }
  33. - (void)setBuyingPrice:(float)price{
  34. _buyingPrice = price;
  35. [self notifyObservers];
  36. }
  37. - (double)getBuyingPrice{
  38. return _buyingPrice;
  39. }
  40. @end

目标类持有一个可变数组,用来保存观察自己的观察者们;并且还提供了增加,删除观察者的接口,也提供了通知所有观察者的接口。

而且它持有一个数据buyingPrice,这个数据就是让外部观察者观察的数据。尤其注意它向外界提供的setBuyingPrice:方法:当外部调用这个方法,也就是要更新buyingPrice这个数据时,目标类调用了notifyObservers方法来告知当前所有观察自己的观察者们:我更新了。

getBuyingPrice就是用来返回当前的buyingPrice的值的,一般是在观察者们收到更新通知后,主动调动这个方法获取的(具体看上面Investor类的实现)。

OK,现在抽象目标类定义好了,下面我们看一下具体目标类FinancialAdviser

  1. //================== FinancialAdviser.h ==================
  2. @interface FinancialAdviser : Subject
  3. @end
  4. //================== FinancialAdviser.m ==================
  5. @implementation FinancialAdviser
  6. @end

因为所有的接口的事先已经在Subject类定义好了,所以我们只需新建一个我们需要的子类即可(如果有不同于父类的操作的话还是可以按照自己的方式定义)。

下面我们看一下观察者的机制是如何实现的:

  1. FinancialAdviser *fa = [[FinancialAdviser alloc] init];
  2. Investor *iv1 = [[Investor alloc] initWithSubject:fa];
  3. NSLog(@"====== first advice ========");
  4. [fa setBuyingPrice:1.3];
  5. Investor *iv2 = [[Investor alloc] initWithSubject:fa];
  6. Investor *iv3 = [[Investor alloc] initWithSubject:fa];
  7. NSLog(@"====== second advice ========");
  8. [fa setBuyingPrice:2.6];

从代码中可以看到,我们最开始向FinancialAdviser(具体目标类)添加了一个具体观察者类的实例iv1,然后FinancialAdviser的实例fa便通知了所有观察者(此时的观察者只有iv1)。

后面我们继续向fa添加了iv2iv3后发送通知。此时三个观察者都收到了消息。

在下面的日至输出中也可以看到,内存地址0x600003094c00就是iv10x6000030836800x600003083690就是iv2iv3

  1. ====== first advice ========
  2. investor 0x600003094c00 buy stock of price:1.30
  3. ====== second advice ========
  4. investor 0x600003094c00 buy stock of price:2.60
  5. investor 0x600003083680 buy stock of price:2.60
  6. investor 0x600003083690 buy stock of price:2.60

代码对应的类图

导出图片Mon Apr 13 2020 15_33_41 GMT+0800 (中国标准时间).png-226.6kB

优点

缺点

iOS SDK中的应用


❤模板方法模式

定义

在模板模式(Template Method Pattern)中,定义一个操作中的算法的框架,而将一些步骤的执行延迟到子类中,使得子类可以在不改变算法的结构的前提下即可重新定义该算法的某些特定步骤。

适用场景

通常一个算法需要几个执行步骤来实现,而有时我们需要定义几种执行步骤一致,但是却可能在某个步骤的实现略有差异的算法。也就是说我们既需要复用实现相同的步骤,也可以通过在某个步骤的不同实现来灵活扩展出更多不同的算法。

在这种场景下,我们可以使用模板方法模式:定义好一个算法的框架,在父类实现可以复用的算法步骤,而将需要扩展和修改其他步骤的任务推迟给子类进行。

成员与类图

成员

模板方法模式的成员除了客户端以外,只有两个成员:

模式类图

导出图片Mon Apr 13 2020 15_30_18 GMT+0800 (中国标准时间).png-125.3kB

由上图可以看出,Algorithmexcute方法是算法接口,它在内部调用了三个步骤方法:step1,step2,step3。而step2是未暴露在外部的,因为这个步骤是需要各个子类复用的。因此Algorithm只将step1step3暴露了出来以供子类来调用。

代码示例

场景概述

模拟一个制作两种热饮的场景:热拿铁,热茶。

这三种热饮的制作步骤是一致的,都是三个步骤:

虽然制作步骤是一致的,但是不同种类的热饮在每一步可能是不同的:咖啡和茶叶主成分是咖啡粉和茶叶;而辅助成分:茶叶可以不添加,而拿铁还需添加牛奶。

而第一步是相同的:准备热水。

根据上面对模板方法模式的介绍,像这样算法步骤相同,算法步骤里的实现可能相同或不同的场景我们可以使用模板方法模式。下面我们看一下如何用代码来模拟该场景。

代码实现

算法类(也就是模板)

因为热饮的第一步都是一致的(准备热水),所以第一步骤的接口没有暴露出来给子类实现,而是直接在当前类实现了,这也就是模板方法的一个可以复用代码的优点

  1. //================== HotDrink.h ==================
  2. @interface HotDrink : NSObject
  3. //暴露了制作过程,这个接口内部调用了热饮的所有制作步骤方法
  4. - (void)makingProcess;
  5. //需要子类按照自己方式实现添加主成分
  6. - (void)addMainMaterial;
  7. //需要子类按照自己方式实现添加辅助成分
  8. - (void)addIngredients;
  9. @end
  10. //================== HotDrink.m ==================
  11. @implementation HotDrink
  12. - (void)makingProcess{
  13. NSLog(@" ===== Begin to making %@ ===== ", NSStringFromClass([self class]));
  14. [self boilWater];
  15. [self addMainMaterial];
  16. [self addIngredients];
  17. }
  18. - (void)prepareHotWater{
  19. NSLog(@"prepare hot water");
  20. }
  21. - (void)addMainMaterial{
  22. NSLog(@"implemetation by subClasses");
  23. }
  24. - (void)addIngredients{
  25. NSLog(@"implemetation by subClasses");
  26. }
  27. - (void)makingProcess{
  28. //准备热水
  29. [self prepareHotWater];
  30. //添加主成分
  31. [self addMainMaterial];
  32. //添加辅助成分
  33. [self addIngredients];
  34. }
  35. @end

热茶:

因为热饮的第一步都是一致的(准备热水),所以第一步骤的接口没有暴露出来给子类实现,而是直接在当前类实现了,这也就是模板方法的一个可以复用代码的优点

  1. //================== HotDrinkTea.h ==================
  2. @interface HotDrinkTea : HotDrink
  3. @end
  4. //================== HotDrinkTea.m ==================
  5. @implementation HotDrinkTea
  6. - (void)addMainMaterial{
  7. NSLog(@"add tea leaf");
  8. }
  9. - (void)addIngredients{
  10. NSLog(@"add nothing");
  11. }
  12. @end

热拿铁:

  1. //================== HotDrinkLatte.h ==================
  2. @interface HotDrinkLatte : HotDrink
  3. @end
  4. //================== HotDrinkLatte.m ==================
  5. @implementation HotDrinkLatte
  6. - (void)addMainMaterial{
  7. NSLog(@"add ground coffee");
  8. }
  9. - (void)addIngredients{
  10. NSLog(@"add milk");
  11. }
  12. @end

主程序调用并输出:

  1. ===== Begin to making HotDrinkTea =====
  2. prepare hot water
  3. add tea leaf
  4. add nothing
  5. ===== Begin to making HotDrinkLatte =====
  6. prepare hot water
  7. add ground coffee
  8. add milk

上面的日至输出准确无误地反映了我们所定义的这两种热饮制作过程:

代码对应的类图

导出图片Mon Apr 13 2020 15_30_37 GMT+0800 (中国标准时间).png-202.7kB

优点

缺点

iOS SDK中的应用


❤策略模式

定义

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。

适用场景

有时候在实现某一个功能的时可能会有多个方案:我们需要让系统可以动态灵活地更换方案;而且也能够让开发者方便地增加新的方案或删除旧的方案。

如果我们将所有的方案硬编码在同一个类中,那么在今后修改,添加,删除某个方案的时候就会改动原有类,这是违反开闭原则的。

其实我们可以定义一些独立的类来封装不同的解决方案,每一个类封装一个具体的方案,这些不同的方案就是我们所说的策略。而且我们可以用一个抽象的策略类来保证这些策略的一致性,这就是策略模式的设计方案。

成员与类图

成员

策略模式除了客户端之外共有三个成员:

模式类图

导出图片Mon Apr 13 2020 15_30_50 GMT+0800 (中国标准时间).png-159.2kB

代码示例

场景概述

模拟一个两个整数可以随意替换加减乘除算法的场景。

在该场景中,传入的两个整数参数是不变的,但是对于这两个整数的具体操作可以灵活切换,那么我们可以使用策略模式:将每个操作(算法)封装起来,在需要替换的时候将Context类持有的具体策略实例更新即可。

代码实现

首先我们定义好抽象策略类和具体策略类:

因为是针对两个整数的操作,所以在抽象策略类中,我们只需定义一个传入两个整数的接口即可。

抽象策略类:

  1. //================== TwoIntOperation.h ==================
  2. @interface TwoIntOperation : NSObject
  3. - (int)operationOfInt1:(int)int1 int2:(int)int2;
  4. @end
  5. //================== TwoIntOperation.m ==================
  6. @implementation TwoIntOperation
  7. - (int)operationOfInt1:(int)int1 int2:(int)int2{
  8. //implenting by sub classes;
  9. return 0;
  10. }
  11. @end

接着我们根据加减乘除四种运算,来分别定义四个具体策略类:

  1. //================== 加法策略类==================
  2. @interface TwoIntOperationAdd : TwoIntOperation
  3. @end
  4. @implementation TwoIntOperationAdd
  5. - (int)operationOfInt1:(int)int1 int2:(int)int2{
  6. NSLog(@"==== adding ====");
  7. return int1 + int2;
  8. }
  9. @end
  10. //================== 减法策略类 ==================
  11. @interface TwoIntOperationSubstract : TwoIntOperation
  12. @end
  13. @implementation TwoIntOperationSubstract
  14. - (int)operationOfInt1:(int)int1 int2:(int)int2{
  15. NSLog(@"==== Substract ====");
  16. return int1 - int2;
  17. }
  18. @end
  19. //================== 乘法策略类 ==================
  20. @interface TwoIntOperationMultiply : TwoIntOperation
  21. @end
  22. @implementation TwoIntOperationMultiply
  23. - (int)operationOfInt1:(int)int1 int2:(int)int2{
  24. NSLog(@"==== multiply ====");
  25. return int1 * int2;
  26. }
  27. @end
  28. //================== 除法策略类 ==================
  29. @interface TwoIntOperationDivision : TwoIntOperation
  30. @end
  31. @implementation TwoIntOperationDivision
  32. - (int)operationOfInt1:(int)int1 int2:(int)int2{
  33. NSLog(@"==== division ====");
  34. return int1/int2;
  35. }
  36. @end

环境类

  1. //================== Context.h ==================
  2. @interface Context : NSObject
  3. - (instancetype)initWithOperation: (TwoIntOperation *)operation;
  4. - (void)setOperation:(TwoIntOperation *)operation;
  5. - (int)excuteOperationOfInt1:(int)int1 int2:(int)int2;
  6. @end
  7. //================== Context.m ==================
  8. @implementation Context
  9. {
  10. TwoIntOperation *_operation;
  11. }
  12. - (instancetype)initWithOperation: (TwoIntOperation *)operation{
  13. self = [super init];
  14. if (self) {
  15. //injection from instane initialization
  16. _operation = operation;
  17. }
  18. return self;
  19. }
  20. - (void)setOperation:(TwoIntOperation *)operation{
  21. //injection from setting method
  22. _operation = operation;
  23. }
  24. - (int)excuteOperationOfInt1:(int)int1 int2:(int)int2{
  25. //return value by constract strategy instane
  26. return [_operation operationOfInt1:int1 int2:int2];
  27. }
  28. @end

Context类在构造器(init方法)注入了一个具体策略实例并持有它,而且Context也提供了set方法,让外部注入进来具体策略类的实例。

而策略的具体执行是通过Context的接口excuteOperationOfInt1:int2。这个接口是提供给客户端调用的;而且在它的内部其实调用的是当前持有的策略实例的执行策略的方法。

所以如果想使用哪种策略,只要将具体策略的实例传入到Context实例即可。

客户端使用:

  1. int int1 = 6;
  2. int int2 = 3;
  3. NSLog(@"int1: %d int2: %d",int1,int2);
  4. //Firstly, using add operation
  5. TwoIntOperationAdd *addOperation = [[TwoIntOperationAdd alloc] init];
  6. Context *ct = [[Context alloc] initWithOperation:addOperation];
  7. int res1 = [ct excuteOperationOfInt1:int1 int2:int2];
  8. NSLog(@"result of adding : %d",res1);
  9. //Changing to multiple operation
  10. TwoIntOperationMultiply *multiplyOperation = [[TwoIntOperationMultiply alloc] init];
  11. [ct setOperation:multiplyOperation];
  12. int res2 = [ct excuteOperationOfInt1:int1 int2:int2];
  13. NSLog(@"result of multiplying : %d",res2);
  14. //Changing to substraction operation
  15. TwoIntOperationSubstract *subOperation = [[TwoIntOperationSubstract alloc] init];
  16. [ct setOperation:subOperation];
  17. int res3 = [ct excuteOperationOfInt1:int1 int2:int2];
  18. NSLog(@"result of substracting : %d",res3);
  19. //Changing to division operation
  20. TwoIntOperationDivision *divisionOperation = [[TwoIntOperationDivision alloc] init];
  21. [ct setOperation:divisionOperation];
  22. int res4 = [ct excuteOperationOfInt1:int1 int2:int2];
  23. NSLog(@"result of dividing : %d",res4);

看一下日至输出:

  1. [13431:1238320] int1: 6 int2: 3
  2. [13431:1238320] ==== adding ====
  3. [13431:1238320] result of adding : 9
  4. [13431:1238320] ==== multiply ====
  5. [13431:1238320] result of multiplying : 18
  6. [13431:1238320] ==== Substract ====
  7. [13431:1238320] result of substracting : 3
  8. [13431:1238320] ==== division ====
  9. [13431:1238320] result dividing : 2

在上面的例子中,首先我们要使用加法,所以 实例化了加法策略类并传入到了Context类的构造器中。

而后续的乘法,减法,除法的更换,则是分别将它们的策略实例传入到了Context的set方法中,并执行即可。

代码对应的类图

导出图片Mon Apr 13 2020 15_31_09 GMT+0800 (中国标准时间).png-163.3kB

优点

缺点

❤责任链模式

定义

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

适用场景

成员与类图

成员

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

模式类图

导出图片Mon Apr 13 2020 15_31_26 GMT+0800 (中国标准时间).png-160.8kB

代码示例

场景概述

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

  1. //================== DispenseChainNode.h ==================
  2. @interface DispenseChainNode : NSObject <DispenseProtocol>
  3. {
  4. @protected DispenseChainNode *_nextChainUnit;
  5. }
  6. - (void)setNextChainUnit:(DispenseChainNode *)chainUnit;
  7. @end
  8. //================== DispenseChainNode.m ==================
  9. @implementation DispenseChainNode
  10. - (void)setNextChainNode:(DispenseChainNode *)chainNode{
  11. _nextChainNode = chainNode;
  12. }
  13. - (void)dispense:(int)amount{
  14. return;
  15. }
  16. @end
  • DispenseChainNode是责任链节点,也就是具体处理者的父类,它持有DispenseChainNode的实例,用来保存当前节点的下一个节点。这个下一个节点的实例是通过setNextChainNode:方法注入进来的
    而且,DispenseChainNode遵循<DispenseProtocol>协议,这个协议只有一个方法,就是dispense:方法,每个节点都实现这个方法来对输入的金额做处理。(dispense 单词的意思是分配,分发)

50,20,10面额的具体处理者:

  1. //================== 50面额的具体处理者 ==================
  2. @interface DispenseChainNodeFor50Yuan : DispenseChainNode
  3. @end
  4. @implementation DispenseChainNodeFor50Yuan
  5. - (void)dispense:(int)amount{
  6. int unit = 50;
  7. if (amount >= unit) {
  8. int count = amount/unit;
  9. int remainder = amount % unit;
  10. NSLog(@"Dispensing %d of %d",count,unit);
  11. if (remainder != 0) {
  12. [_nextChainNode dispense:remainder];
  13. }
  14. }else{
  15. [_nextChainNode dispense:amount];
  16. }
  17. }
  18. @end
  19. //================== 20面额的具体处理者 ==================
  20. @interface DispenseChainNodeFor20Yuan : DispenseChainNode
  21. @end
  22. @implementation DispenseChainNodeFor20Yuan
  23. - (void)dispense:(int)amount{
  24. int unit = 20;
  25. if (amount >= unit) {
  26. int count = amount/unit;
  27. int remainder = amount % unit;
  28. NSLog(@"Dispensing %d of %d",count,unit);
  29. if (remainder != 0) {
  30. [_nextChainNode dispense:remainder];
  31. }
  32. }else{
  33. [_nextChainNode dispense:amount];
  34. }
  35. }
  36. @end
  37. //================== 10面额的具体处理者 ==================
  38. @interface DispenseChainNodeFor10Yuan : DispenseChainNode
  39. @end
  40. @implementation DispenseChainNodeFor10Yuan
  41. - (void)dispense:(int)amount{
  42. int unit = 10;
  43. if (amount >= unit) {
  44. int count = amount/unit;
  45. int remainder = amount % unit;
  46. NSLog(@"Dispensing %d of %d",count,unit);
  47. if (remainder != 0) {
  48. [_nextChainNode dispense:remainder];
  49. }
  50. }else{
  51. [_nextChainNode dispense:amount];
  52. }
  53. }
  54. @end

上面三个具体处理者在dispense:方法的处理都是类似的:

首先查看当前值是否大于面额:

ATM类来把这些节点串起来:

  1. //================== ATMDispenseChain.h ==================
  2. @interface ATMDispenseChain : NSObject<DispenseProtocol>
  3. @end
  4. //================== ATMDispenseChain.m ==================
  5. @implementation ATMDispenseChain
  6. {
  7. DispenseChainNode *_chainNode;
  8. }
  9. - (instancetype)init{
  10. self = [super init];
  11. if(self){
  12. DispenseChainNodeFor50Yuan *chainNode50 = [[DispenseChainNodeFor50Yuan alloc] init];
  13. DispenseChainNodeFor20Yuan *chainNode20 = [[DispenseChainNodeFor20Yuan alloc] init];
  14. DispenseChainNodeFor10Yuan *chainNode10 = [[DispenseChainNodeFor10Yuan alloc] init];
  15. _chainNode = chainNode50;
  16. [_chainNode setNextChainNode:chainNode20];
  17. [chainNode20 setNextChainNode:chainNode10];
  18. }
  19. return self;
  20. }
  21. - (void)dispense:(int)amount{
  22. NSLog(@"==================================");
  23. NSLog(@"ATM start dispensing of amount:%d",amount);
  24. if (amount %10 != 0) {
  25. NSLog(@"Amount should be in multiple of 10");
  26. return;
  27. }
  28. [_chainNode dispense:amount];
  29. }
  30. @end

ATMDispenseChain这个类在初始化的时候就将三个具体处理者并按照50,20,10的顺序连接起来,并持有一个DispenseChainNode的指针指向当前的具体处理者(也就是责任链的第一个节点,面额50的具体处理者,因为面额的处理是从50开始的)。

业务方使用:

  1. ATMDispenseChain *atm = [[ATMDispenseChain alloc] init];
  2. [atm dispense:230];
  3. [atm dispense:70];
  4. [atm dispense:40];
  5. [atm dispense:10];
  6. [atm dispense:8];
  7. //查看输出:
  8. ==================================
  9. ATM start dispensing of amount:230
  10. Dispensing 4 of 50
  11. Dispensing 1 of 20
  12. Dispensing 1 of 10
  13. ==================================
  14. ATM start dispensing of amount:70
  15. Dispensing 1 of 50
  16. Dispensing 1 of 20
  17. ==================================
  18. ATM start dispensing of amount:40
  19. Dispensing 2 of 20
  20. ==================================
  21. ATM start dispensing of amount:10
  22. Dispensing 1 of 10
  23. ==================================
  24. ATM start dispensing of amount:8
  25. Amount should be in multiple of 10

需要注意的是,该代码示例中的责任链类(ATMDispenseChain)并没有在上述责任链模式的成员中。不过此处不必做过多纠结,我们在这里只是在业务上稍微多做一点处理罢了。其实也完全可以不封装这些节点,直接逐个调用setNextChainNode:方法组装责任链,然后将任务交给第一个处理者即可。

重构代码

我们回去看一下这三个具体处理者在dispense:方法的处理是非常相似的,他们的区别只有处理的面额数值的不同:而我们其实是创建了针对这三个面值的类,并将面值(50,20,10)硬编码在了这三个类中。这样做是有缺点的,因为如果后面的面额大小变了,或者增加或者减少面额的话我们会修改这些类或添加删除这些类(即使这也比不使用责任链模式的if-else要好一些)。

因此我们可以不创建这些与面额值硬编码的具体处理类,而是在初始化的时候直接将面额值注入到构造方法里面即可!这样一来,我们可以随意调整和修改面额了。

DispenseChainNode添加传入面额值的初始化方法以及面额值的成员变量:

  1. //================== ADispenseChainNode.h ==================
  2. @interface DispenseChainNode : NSObject <DispenseProtocol>
  3. {
  4. @protected DispenseChainNode *_nextChainNode;
  5. @protected int _dispenseValue;
  6. }
  7. //可以根据需求随意生成不同面额的具体处理者
  8. - (instancetype)initWithDispenseValue:(int)dispenseValue;
  9. - (void)setNextChainNode:(DispenseChainNode *)chainNode;
  10. @end
  11. //================== ADispenseChainNode.m ==================
  12. @implementation DispenseChainNode
  13. - (instancetype)initWithDispenseValue:(int)dispenseValue
  14. {
  15. self = [super init];
  16. if (self) {
  17. _dispenseValue = dispenseValue;
  18. }
  19. return self;
  20. }
  21. - (void)setNextChainNode:(DispenseChainNode *)chainNode{
  22. _nextChainNode = chainNode;
  23. }
  24. - (void)dispense:(int)amount{
  25. if (amount >= _dispenseValue) {
  26. int count = amount/_dispenseValue;
  27. int remainder = amount % _dispenseValue;
  28. NSLog(@"Dispensing %d of %d",count,_dispenseValue);
  29. if (remainder != 0) {
  30. [_nextChainNode dispense:remainder];
  31. }
  32. }else{
  33. [_nextChainNode dispense:amount];
  34. }
  35. }
  36. @end

既然DispenseChainNode可以根据不同的面额值生成处理不同面额的具体处理者实例,那么对于串联多个具体处理者的类ATMDispenseChain是不是也可以添加一个注入面额数组的初始化方法呢?比如输入[50,20,10]的数组就可以生成50,20,10面额的具体处理者了;而且数组是有序的,传入数组的元素顺序就可以是责任链中节点的顺序。

传入数组的节点串联类:

  1. //================== ATMDispenseChain.m ==================
  2. @implementation ATMDispenseChain
  3. {
  4. DispenseChainNode *_firstChainNode;
  5. DispenseChainNode *_finalChainNode;
  6. int _minimumValue;
  7. }
  8. //需要从外部传入面额值的数组,在这个方法里面根据传入的数组构造了整条责任链
  9. - (instancetype)initWithDispenseNodeValues:(NSArray *)nodeValues{
  10. self = [super init];
  11. if(self){
  12. NSUInteger length = [nodeValues count];
  13. [nodeValues enumerateObjectsUsingBlock:^(NSNumber * nodeValue, NSUInteger idx, BOOL * _Nonnull stop) {
  14. DispenseChainNode *iterNode = [[DispenseChainNode alloc] initWithDispenseValue:[nodeValue intValue]];
  15. if (idx == length - 1 ) {
  16. _minimumValue = [nodeValue intValue];
  17. }
  18. if (!self->_firstChainNode) {
  19. //because this chain is empty, so the first node and the final node will refer the same node instance
  20. self->_firstChainNode = iterNode;
  21. self->_finalChainNode = self->_firstChainNode;
  22. }else{
  23. //appending the next node, and setting the new final node
  24. [self->_finalChainNode setNextChainNode:iterNode];
  25. self->_finalChainNode = iterNode;
  26. }
  27. }];
  28. }
  29. return self;
  30. }
  31. - (void)dispense:(int)amount{
  32. NSLog(@"==================================");
  33. NSLog(@"ATM start dispensing of amount:%d",amount);
  34. if (amount % _minimumValue != 0) {
  35. NSLog(@"Amount should be in multiple of %d",_minimumValue);
  36. return;
  37. }
  38. [ _firstChainNode dispense:amount];
  39. }
  40. @end

业务方使用:

  1. NSArray *dispenseNodeValues = @[@(100),@(50),@(20),@(10)];
  2. ATMDispenseChain *atm = [[ATMDispenseChain alloc] initWithDispenseNodeValues:dispenseNodeValues];
  3. [atm dispense:230];
  4. [atm dispense:70];
  5. [atm dispense:40];
  6. [atm dispense:10];
  7. [atm dispense:8];
  8. //输出:
  9. ==================================
  10. ATM start dispensing of amount:230
  11. Dispensing 2 of 100
  12. Dispensing 1 of 20
  13. Dispensing 1 of 10
  14. ==================================
  15. ATM start dispensing of amount:70
  16. Dispensing 1 of 50
  17. Dispensing 1 of 20
  18. ==================================
  19. ATM start dispensing of amount:40
  20. Dispensing 2 of 20
  21. ==================================
  22. ATM start dispensing of amount:10
  23. Dispensing 1 of 10
  24. ==================================
  25. ATM start dispensing of amount:8
  26. Amount should be in multiple of 10

代码对应的类图

重构前:
导出图片Mon Apr 13 2020 15_31_50 GMT+0800 (中国标准时间).png-229.7kB

重构后:
导出图片Mon Apr 13 2020 15_31_59 GMT+0800 (中国标准时间).png-220.2kB

优点

缺点

iOS SDK中的应用

参考

面向对象设计的设计模式(三):行为型模式
行为模式


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注