面向对象设计的设计模式(三):行为型模式(附 Demo & UML类图)

本篇是面向对象设计系列文章的第四篇,讲解的是设计模式中的7个比较常见的行为型模式(按照本文讲解顺序排列):java

  • 模板方法模式
  • 策略模式
  • 责任链模式
  • 状态模式
  • 命令模式
  • 观察者模式
  • 中介者模式

一. 模板方法模式

定义

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

适用场景

一般一个算法须要几个执行步骤来实现,而有时咱们须要定义几种执行步骤一致,可是却可能在某个步骤的实现略有差别的算法。也就是说咱们既须要复用实现相同的步骤,也能够经过在某个步骤的不一样实现来灵活扩展出更多不一样的算法。git

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

如今咱们清楚了模板方法模式的适用场景,下面看一下这个模式的成员和类图。github

成员与类图

成员

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

  • 算法类(Algorithm):算法类负责声明算法接口,算法步骤接口。并实现可复用的算法步骤接口,且将须要子类实现的接口暴露出来。
  • 具体算法类(Concrete Algorithm):具体算法类负责实现算法类声明的算法步骤接口。

有些参考资料定义这两个成员为Abstract ClassConcrete Class编程

下面经过类图来看一下命令模式各个成员之间的关系:设计模式

模式类图

模板方法模式类图

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

代码示例

场景概述

模拟一个制做三种热饮的场景:热美式咖啡,热拿铁,热茶。安全

场景分析

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

  • 步骤一:准备热水
  • 步骤二:加入主成分
  • 步骤三:加入辅助成分(也能够不加,看具体热饮的种类)

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

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

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

代码实现

首先咱们建立算法类HotDrink

//================== HotDrink.h ==================

@interface HotDrink : NSObject

- (void)makingProcess;

- (void)addMainMaterial;

- (void)addIngredients;

@end



//================== HotDrink.m ==================

@implementation HotDrink

- (void)makingProcess{
    
    NSLog(@" ===== Begin to making %@ ===== ", NSStringFromClass([self class]));
    
    [self boilWater];
    [self addMainMaterial];
    [self addIngredients];
}


- (void)prepareHotWater{
    
    NSLog(@"prepare hot water");
}


- (void)addMainMaterial{
    
    NSLog(@"implemetation by subClasses");
}


- (void)addIngredients{
    
    NSLog(@"implemetation by subClasses");
}


@end
复制代码

HotDrink向外部暴露了一个制做过程的接口makingProcess,这个接口内部调用了热饮的全部制做步骤方法:

- (void)makingProcess{
         
     //准备热水 
    [self prepareHotWater];
    
    //添加主成分
    [self addMainMaterial];
    
    //添加辅助成分
    [self addIngredients];
}
复制代码

HotDrink只向外暴露了这三个步骤中的两个须要子类按照本身方式实现的接口:

//添加主成分
- (void)addMainMaterial;

//添加辅助成分
- (void)addIngredients;
复制代码

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

OK,咱们如今建立好了算法类,那么根据上面的需求,咱们接着建立三个具体算法类:

  • HotDrinkTea : 热茶
  • HotDrinkLatte : 热拿铁
  • HotDrinkAmericano: 热美式
//================== HotDrinkTea.h ==================

@interface HotDrinkTea : HotDrink

@end



//================== HotDrinkTea.m ==================

@implementation HotDrinkTea


- (void)addMainMaterial{
    
    NSLog(@"add tea leaf");
}


- (void)addIngredients{
    
    NSLog(@"add nothing");
}


@end
复制代码

热茶在addMainMaterial步骤里面是添加了茶叶,而在addIngredients步骤没有作任何事情(这里先假定是纯的茶叶)。

相似地,咱们看一下两种热咖啡的实现。首先是热拿铁HotDrinkLatte:

//================== HotDrinkLatte.h ==================

@interface HotDrinkLatte : HotDrink

@end



//================== HotDrinkLatte.m ==================

@implementation HotDrinkLatte

- (void)addMainMaterial{
    
    NSLog(@"add ground coffee");
}


- (void)addIngredients{
    
    NSLog(@"add milk");
}


@end
复制代码

热拿铁在addMainMaterial步骤里面是添加了咖啡粉,而在addIngredients步骤添加了牛奶。

下面再看一下热美式HotDrinkAmericano

//================== HotDrinkAmericano.h ==================

@interface HotDrinkAmericano : HotDrink

@end



//================== HotDrinkAmericano.m ==================

@implementation HotDrinkAmericano

- (void)addMainMaterial{
    
    NSLog(@"add ground coffee");
}


- (void)addIngredients{
    
    NSLog(@"add nothing");
}

@end
复制代码

热美式在addMainMaterial步骤里面是添加了咖啡粉,而在addIngredients步骤没有作任何事,由于美式就是纯的咖啡,理论上除了水和咖啡不须要添加任何其余东西。

到如今三种热饮类建立好了,咱们如今分别制做这三种热饮,并看一下日至输出:

===== Begin to making HotDrinkTea =====
prepare hot water
add tea leaf
add nothing
===== Begin to making HotDrinkLatte =====
prepare hot water
add ground coffee
add milk
===== Begin to making HotDrinkAmericano =====
prepare hot water
add ground coffee
add nothing
复制代码

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

  • 热茶:准备热水 + 茶叶
  • 热拿铁:准备热水 + 咖啡 + 牛奶
  • 热美式:准备热水 + 咖啡

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

代码对应的类图

模板方法模式代码示例类图

优势

  • 复用性高:将相同的代码放在父类中,而不一样的部分则由子类实现
  • 扩展性高:能够经过建立不一样的子类来扩展不一样的算法
  • 符合开闭原则:可变与不可变的部分分离,并且不一样的可变部分(子类)也是相互分离的,因此符合了开闭原则

缺点

  • 致使类的个数增长:对于每个算法实现都须要一个子类,若是实现过多的话会致使类的个数增长
  • 由继承关系致使的缺点:若是父类须要增长或减小它的行为,则全部的子类都须要同步修改一次

iOS SDK 和 JDK中的应用

  • 在 iOS SDK 中,咱们能够重写 UIViewdrawRect:方法能够自定义绘图,是模板方法模式的一种实践。
  • 在JDK中,java.lang.Runnable是使用JDK的经典场景:Runnable接口能够做为抽象的命令,而实现了Runnable的线程便是具体的命令。

二. 策略模式

定义

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

适用场景

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

若是咱们将全部的方案硬编码在同一个类中,那么在从此修改,添加,删除某个方案的时候就会改动原有类,这是违反开闭原则的。

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

如今咱们清楚了策略模式的适用场景,下面看一下策略模式的成员和类图。

成员与类图

成员

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

  • 环境类(Context):环境类内部持有一个具体策略类的实例,这个实例就是当前的策略,能够供客户端使用
  • 抽象策略类(Strategy):抽象策略类声明具体策略类须要实现的接口,这个接口同时也是提供给客户端调用的接口
  • 具体策略类(Concrete Strategy):具体策略类实现抽象策略类声明的接口,每一个具体策略类都有本身独有的实现方式,即表明不一样策略

下面咱们经过类图来看一下各个成员之间的关系。

模式类图

策略模式类图

代码示例

场景概述

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

场景分析

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

代码实现

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

由于是针对两个整数的操做,因此在抽象策略类中,咱们只需定义一个传入两个整数的接口便可。

抽象策略类TwoIntOperation:

//================== TwoIntOperation.h ==================

@interface TwoIntOperation : NSObject

- (int)operationOfInt1:(int)int1 int2:(int)int2;

@end



//================== TwoIntOperation.m ==================

@implementation TwoIntOperation

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    //implenting by sub classes;
    return 0;
}

@end
复制代码

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

加法TwoIntOperationAdd

//================== TwoIntOperationAdd.h ==================

@interface TwoIntOperationAdd : TwoIntOperation

@end



//================== TwoIntOperationAdd.m ==================

@implementation TwoIntOperationAdd

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== adding ====");
    
    return int1 + int2;
}

@end
复制代码

减法TwoIntOperationSubstract

//================== TwoIntOperationSubstract.h ==================

@interface TwoIntOperationSubstract : TwoIntOperation

@end



//================== TwoIntOperationSubstract.m ==================

@implementation TwoIntOperationSubstract

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== Substract ====");
    return int1 - int2;
}
@end
复制代码

乘法TwoIntOperationMultiply:

//================== TwoIntOperationMultiply.h ==================

@interface TwoIntOperationMultiply : TwoIntOperation

@end



//================== TwoIntOperationMultiply.m ==================

@implementation TwoIntOperationMultiply

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== multiply ====");
    
    return int1 * int2;
}

@end
复制代码

除法TwoIntOperationDivision:

//================== TwoIntOperationDivision.h ==================

@interface TwoIntOperationDivision : TwoIntOperation

@end



//================== TwoIntOperationDivision.m ==================

@implementation TwoIntOperationDivision

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== division ====");
    return int1/int2;
}

@end
复制代码

如今关于算法的类都声明好了,咱们最后声明一下 Context 类:

//================== Context.h ==================

@interface Context : NSObject

- (instancetype)initWithOperation: (TwoIntOperation *)operation;

- (void)setOperation:(TwoIntOperation *)operation;

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2;

@end



//================== Context.m ==================

@implementation Context
{
    TwoIntOperation *_operation;
}

- (instancetype)initWithOperation: (TwoIntOperation *)operation{

    self = [super init];
    if (self) {
        //injection from instane initialization
        _operation = operation;
    }
    return self;
}

- (void)setOperation:(TwoIntOperation *)operation{
    
    //injection from setting method
    _operation = operation;
}

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2{
    
    //return value by constract strategy instane
    return [_operation operationOfInt1:int1 int2:int2];
}

@end
复制代码

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

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

因此若是想使用哪一种策略,只要将具体策略的实例传入到Context实例便可。

如今全部的类都定义好了,下面咱们看一下具体如何使用:

int int1 = 6;
int int2 = 3;
    
NSLog(@"int1: %d int2: %d",int1,int2);
    
//Firstly, using add operation
TwoIntOperationAdd *addOperation = [[TwoIntOperationAdd alloc] init];
Context *ct = [[Context alloc] initWithOperation:addOperation];
int res1 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of adding : %d",res1);
    
//Changing to multiple operation
TwoIntOperationMultiply *multiplyOperation = [[TwoIntOperationMultiply alloc] init];
[ct setOperation:multiplyOperation];
int res2 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of multiplying : %d",res2);
    
    
//Changing to substraction operation
TwoIntOperationSubstract *subOperation = [[TwoIntOperationSubstract alloc] init];
[ct setOperation:subOperation];
int res3 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of substracting : %d",res3);
    
    
//Changing to division operation
TwoIntOperationDivision *divisionOperation = [[TwoIntOperationDivision alloc] init];
[ct setOperation:divisionOperation];
int res4 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of dividing : %d",res4);
复制代码

看一下日至输出:

[13431:1238320] int1: 6    int2: 3
[13431:1238320] ==== adding ====
[13431:1238320] result of adding : 9
[13431:1238320] ==== multiply ====
[13431:1238320] result of multiplying : 18
[13431:1238320] ==== Substract ====
[13431:1238320] result of substracting : 3
[13431:1238320] ==== division ====
[13431:1238320] result dividing : 2
复制代码

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

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

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

代码对应的类图

策略模式代码示例类图

优势

  • 策略模式遵循开闭原则,用户能够在不修改原有系统的前提下选择和更换算法
  • 避免使用多重条件判断
  • 能够灵活地增长新的算法或行为
  • 提升算法和策略的安全性:能够封装策略的具体实现,调用者只须要知道不一样策略之间的区别就能够

缺点

  • 客户端必须知道当前全部的具体策略类,并且须要自行决定使用哪个策略类
  • 若是可选的方案过多,会致使策略类数量激增。

iOS SDK 和 JDK中的应用

  • JDK中的Comparator是策略模式的实现,可使用不一样的子类,也就是具体策略来解决不一样的需求。

三. 责任链模式

定义

责任链模式(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遵循<DispenseProtocol>协议,这个协议只有一个方法,就是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,是责任链模式的一种实践。

四. 状态模式

定义

在状态模式(State Pattern):容许一个对象在其内部状态改变时,改变它的行为。

适用场景

一个对象存在多个状态,不一样状态下的行为会有不一样,并且状态之间能够相互转换。

若是咱们经过if else来判断对象的状态,那么代码中会包含大量与对象状态有关的条件语句,并且在添加,删除和更改这些状态的时候回比较麻烦;而若是使用状态模式。将状态对象分散到不一样的类中,则能够消除 if...else等条件选择语句。

如今咱们清楚了状态模式的适用场景,下面看一下状态模式的成员和类图。

成员与类图

成员

状态模式一共只有四个成员:

  • 环境类(Context):环境类引用了具体状态的实例。环境类持有的具体状态就是当前的状态,能够经过 set 方法将状态实例注入到环境类中。
  • 抽象状态类(State):抽象状态类声明具体状态类须要实现的接口。
  • 具体状态类(Concrete State):具体状态类实现抽象状态类声明的接口。

下面经过类图来看一下各个成员之间的关系:

模式类图

状态模式类图

代码示例

场景概述

模拟一个程序员一天的生活,他有四个状态:

  1. 醒着
  2. 睡觉中
  3. 写代码中
  4. 吃饭中

看这几个状态应该是个很是爱写代码的程序员 ^ ^

场景分析

这个程序员有四个状态,可是有些状态之间是没法切换的:好比从睡觉是没法切换到写代码的(由于须要切换到醒着,而后才能到写代码);从吃饭中是没法切换到醒着的,由于已经醒着了。

若是咱们不使用状态模式,在切换状态的时候可能会写很多if-else判断,并且随着状态的增多,这些分支会变得更多,难以维护。

而若是咱们使用状态模式,则能够将每一个状态封装到一个类中,便于管理;并且在增长或减小状态时也会很方便。

下面咱们看一下如何用代码来模拟该场景。

代码实现

首先咱们定义状态类:

//================== State.h ==================

@interface State : NSObject<ActionProtocol>
{
    @protected Coder *_coder;
}

- (instancetype)initWithCoder:(Coder *)coder;

@end



//================== State.m ==================

@implementation State

- (instancetype)initWithCoder:(Coder *)coder{
    
    self = [super init];
    if (self) {
        _coder = coder;
    }
    return self;
}

@end
复制代码

状态类持有一个coder,也就是程序员的实例,并遵循了ActionProtocol

//================== ActionProtocol.h ==================

@protocol ActionProtocol <NSObject>

@optional;

- (void)wakeUp;

- (void)fallAsleep;

- (void)startCoding;

- (void)startEating;

@end
复制代码

ActionProtocol定义了程序员的一些动做,这些动做是程序员的平常活动,也是触发状态切换的动做,所以State也须要遵循这个协议,由于它的子类须要实现这些操做。

接下来咱们看一下State的子类,根据上面说的四种状态,咱们定义下面四个状态子类:

StateAwake:

//================== StateAwake.h ==================

@interface StateAwake : State

@end

@implementation StateAwake

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}

- (void)startCoding{
    
    NSLog(@"Change state from awake to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{
    
    NSLog(@"Change state from awake to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{
    
    NSLog(@"Change state from awake to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end
复制代码

StateSleeping:

//================== StateSleeping.h ==================

@interface StateSleeping : State

@end



//================== StateSleeping.m ==================

@implementation StateSleeping

- (void)wakeUp{
    
    NSLog(@"Change state from sleeping to awake");
    [_coder setState:(State *)[_coder stateAwake]];
}


- (void)startCoding{
    
    NSLog(@"Already sleeping, can not change state to coding");
}

- (void)startEating{
    
    NSLog(@"Already sleeping, can change state to eating");
}


- (void)fallAsleep{
    
    NSLog(@"Already sleeping, can not change state to sleeping again");
}

@end
复制代码

StateEating:

//================== StateEating.h ==================

@interface StateEating : State

@end



//================== StateEating.m ==================

@implementation StateEating

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{
    
    NSLog(@"New idea came out! change state from eating to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{
    
    NSLog(@"Already eating, can not change state to eating again");
}


- (void)fallAsleep{
    
    NSLog(@"Too tired, change state from eating to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}



@end
复制代码

"StateCoding":

//================== StateCoding.h ==================

@interface StateCoding : State

@end



//================== StateCoding.m ==================

@implementation StateCoding

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{
    
    NSLog(@"Already coding, can not change state to coding again");
}

- (void)startEating{
    
    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{
    
    NSLog(@"Too tired, change state from coding to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end
复制代码

从上面的类能够看出,在有些状态之间的转换是失效的,有些是能够的。 好比相同状态的切换是无效的;从 sleeping没法切换到coding,可是反过来能够,由于可能写代码累了就直接睡了。

下面咱们看一下程序员类的实现:

//================== Coder.h ==================

@interface Coder : NSObject<ActionProtocol>

@property (nonatomic, strong) StateAwake *stateAwake;
@property (nonatomic, strong) StateCoding *stateCoding;
@property (nonatomic, strong) StateEating *stateEating;
@property (nonatomic, strong) StateSleeping *stateSleeping;

- (void)setState:(State *)state;

@end



//================== Coder.m ==================

@implementation Coder
{
    State *_currentState;
}

- (instancetype)init{
    
    self = [super init];
    if (self) {
        
        _stateAwake = [[StateAwake alloc] initWithCoder:self];
        _stateCoding = [[StateCoding alloc] initWithCoder:self];
        _stateEating = [[StateEating alloc] initWithCoder:self];
        _stateSleeping = [[StateSleeping alloc] initWithCoder:self];
        
        _currentState = _stateAwake;
    }
    return self;
}

- (void)setState:(State *)state{
    
    _currentState = state;
}

- (void)wakeUp{
    
    [_currentState wakeUp];
}

- (void)startCoding{
    
    [_currentState startCoding];
}

- (void)startEating{
    
    [_currentState startEating];
}


- (void)fallAsleep{
    
    [_currentState fallAsleep];
}

@end
复制代码

从上面的代码咱们能够看到,程序员类持有一个当前的状态的实例,在初始化后默认的状态为awake,并对外提供一个setState:的方法来切换状态。并且在初始化方法里,咱们实例化了全部的状态,目的是在切换状态中时使用,详见具体状态类的方法:

- (void)startEating{
    
    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}
复制代码

上面这段代码有点绕,可能须要多看几遍源码才能理解(这里面[_coder stateEating]是调用了coder的一个get方法,返回了stateEating这个实例)。

最后,在程序员的动做方法里面,实际上调用的是当前状态对应的方法(这也就是为什么程序员类和状态类都要遵循ActionProtocol的缘由)。

这样,咱们的状态类,状态子类,程序员类都声明好了。咱们看一下如何使用:

Coder *coder = [[Coder alloc] init];
    
//change to awake.. failed
[coder wakeUp];//Already awake, can not change state to awake again
    
//change to coding
[coder startCoding];//Change state from awake to coding
    
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
    
//change to eat...failed
[coder startEating];//Already sleeping, can change state to eating
    
//change to wake up
[coder wakeUp];//Change state from sleeping to awake

//change wake up...failed
[coder wakeUp];//Already awake, can not change state to awake again
    
//change to eating
[coder startEating];//Change state from awake to eating
    
//change to coding
[coder startCoding];//New idea came out! change state from eating to coding
    
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
复制代码

在上面的代码里,咱们实例化了一个程序员类,接着不断调用一些触发状态改变的方法。咱们把每次状态切换的日至输出注释到了代码右侧,能够看到在一些状态的切换是不容许的:

  • 好比从上到下的第一个[coder wakeUp]:由于程序员对象初始化后默认是awake状态,因此没法切换到相同的状态
  • 好比从上到下的第一个[coder startEating]:在睡觉时是没法直接切换到eating状态;而在后面wake之后,再执行[coder startEating]就成功了。

从上面的例子能够看出,使用状态模式不须要去写if-else,并且若是从此想添加一个状态,只须要再建立一个状态子类,并在新的状态子类添加好对全部状态的处理,并在以前的状态子类中添加上对新状态的处理便可。即使咱们修改了以前定义好的状态子类,可是这样也总比使用庞大的if-else要方便多。

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

代码对应的类图

状态模式代码示例类图

优势

  1. 把各类状态的转换逻辑,分布到不一样的类中,减小相互间的依赖。

缺点

  1. 增长新的状态类须要修改状态转换的源码,并且增长新的行为也要修改原来的状态类(前提是新的行为和原来的状态有关系)。
  2. 过多的状态会增长系统中的类的个数,增长系统的复杂性。

iOS SDK 和 JDK中的应用

  • javax包下的LifyCycle是状态模式的一种实现

五. 命令模式

定义

命令模式(Command Pattern):命令(或请求)被封装成对象。客户端将命令(或请求)对象先传递给调用对象。调用对象再把该命令(或请求)对象传给合适的,可处理该命令(或请求)的对象来作处理。

由定义能够看出,在命令模式中,命令被封装成了对象,而发送命令的客户端与处理命令的接收者中间被调用对象隔开了,这种设计的缘由或者适用的场景是什么样的呢?

适用场景

在有些场景下,任务的处理可能不是须要当即执行的:可能须要记录(日至),撤销或重试(网络请求)。那么在这些场景下,若是任务的请求者和执行者是紧耦合状态下的话就可能会将不少其余执行策略的代码和当即执行的代码混合到一块儿。

这些其余执行策略,咱们暂时称之为控制和管理策略,而若是咱们若是想控制和管理请求,就须要:

  1. 把请求抽象出来
  2. 让另一个角色来负责控制和管理请求的任务

所以命令模式就是为此场景量身打造的,它经过:

  1. 把请求封装成对象
  2. 使用调用者在客户端和请求处理者之间来作一个“拦截”,方便对请求对象作控制和管理。

如今咱们清楚了命令模式的适用场景,下面看一下命令模式的成员和类图。

成员与类图

成员

不包括请求的发起者(客户端),命令模式共有四个成员:

  • 抽象命令类(Command):命令类负责声明命令的接口。
  • 具体命令类(Concrete Command):具体命令类负责实现抽象命令类声明的接口
  • 调用者(Invoker):调用者负责将具体命令类的实例传递给接收者
  • 接收者(Receiver):接收者负责处理命令

下面经过类图来看一下命令模式各个成员之间的关系:

模式类图

命令模式类图

代码示例

场景概述

模拟一个使用遥控器开灯和关灯的例子。

场景分析

在这个例子中,使用遥控器的人就是客户端,TA发起开启或关闭灯的命令给遥控器(调用者)。而后调用者将命令传递给接收者(灯)。

在这里,人是不直接接触灯的,开启和关闭的命令是经过遥控器来作的转发,最后传达给灯来执行。

下面咱们看一下如何用代码来模拟该场景。

代码实现

首先咱们建立接收者,灯类:

//================== Light.h ==================

@interface Light : NSObject

- (void)lightOn;

- (void)lightOff;

@end



//================== Light.m ==================

@implementation Light


- (void)lightOn{
    
    NSLog(@"Light on");
}


- (void)lightOff{
    
    NSLog(@"Light off");
}

@end
复制代码

灯类声明并实现了两个接口:开灯接口和关灯接口,来让外部执行开灯和关灯的操做。

接着咱们建立抽象命令类和具体命令类:

抽象命令类:

//================== Command.h ==================

@interface Command : NSObject

- (void)excute;

@end



//================== Command.m ==================

@implementation Command

@end
复制代码

抽象命令类声明了一个执行命令的接口excute,这个接口由它的子类,也就是具体命令类来实现。

由于这里面只有开灯和关灯两种命令,因此咱们建立两个具体命令类来继承上面的抽象命令类:

开灯命令CommandLightOn

//================== CommandLightOn.h ==================

@interface CommandLightOn : Command

- (instancetype)initWithLight:(Light *)light;

@end


//================== CommandLightOn.m ==================

@implementation CommandLightOn
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{
    
    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{
    
    [_light lightOn];
}
复制代码

关灯命令CommandLightOff

//================== CommandLightOff.h ==================

@interface CommandLightOff : Command

- (instancetype)initWithLight:(Light *)light;

@end



//================== CommandLightOff.m ==================
@implementation CommandLightOff
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{
    
    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{
    
    [_light lightOff];
}
复制代码

咱们能够看到这两个具体命令类分别以本身的方式实现了它们的父类声明的excute接口。

最后咱们建立连接客户端和接收者的调用者类,也就是遥控器类RemoteControl

//================== RemoteControl.h ==================

@interface RemoteControl : NSObject

- (void)setCommand:(Command *)command;

- (void)pressButton;

@end



//================== RemoteControl.m ==================

@implementation RemoteControl
{
    Command *_command;
}


- (void)setCommand:(Command *)command{
    
    _command = command;
}

- (void)pressButton{
    
    [_command excute];
}

@end
复制代码

遥控器类使用set方法注入了具体命令类,并向外提供了pressButton这个方法来内部调用已传入的具体命令类的excute方法。

最后咱们看一下客户端是如何操做这些类的:

//================== client ==================

//init Light and Command instance
//inject light instance into command instance
Light *light = [[Light alloc] init];
CommandLightOn *co = [[CommandLightOn alloc] initWithLight:light];
    
//set command on instance into remote control instance
RemoteControl *rm = [[RemoteControl alloc] init];
[rm setCommand:co];
    
//excute command(light on command)
[rm pressButton];
    

//inject light instance into command off instance
CommandLightOff *cf = [[CommandLightOff alloc] initWithLight:light];

//change to off command
[rm setCommand:cf];

//excute command(light close command)
[rm pressButton];
复制代码

看一下日至输出:

[11851:1190777] Light on
[11851:1190777] Light off
复制代码

从上面的代码能够看到,咱们首先准备好具体命令类的实例,而后将其传递给遥控器类,最后触发遥控器的pressButton方法来间接触发light对象的相应操做。

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

代码对应的类图

命令模式代码示例类图

优势

  • 将命令的发起者和命令的执行者分离,下降系统的耦合度
  • 便于批量处理命令,好比日至队列的实现;便于命令的撤销或重试,好比网络请求等

缺点

  • 须要针对每个命令建立一个命令对象。若是系统中的命令过多,会形成系统中存在大量的命令类,提升系统的复杂度。

iOS SDK 和 JDK中的应用

  • 在JDK中,java.lang.Runnable是使用命令模式的经典场景,Runnable接口能够做为抽象的命令,而实现了Runnable的线程便是具体的命令。

六. 观察者模式

定义

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

适用场景

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

如今咱们清楚了观察者模式的适用场景,下面看一下观察者模式的成员和类图。

成员与类图

成员

观察者模式有四个成员:

  • 目标(Subject):目标是被观察的角色,声明添加和删除观察者以及通知观察者的接口。
  • 具体目标(Concrete Subject):具体目标实现目标类声明的接口,保存全部观察者的实例(经过集合的形式)。在被观察的状态发生变化时,给全部登记过的观察者发送通知。
  • 观察者(Observer):观察者定义具体观察者的更新接口,以便在收到通知时实现一些操做。
  • 具体观察者(Concrete Observer):具体观察者实现抽象观察者定义的更新接口。

下面经过类图来看一下各个成员之间的关系:

模式类图

观察者模式类图

代码示例

场景概述

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

场景分析

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

下面咱们看一下如何用代码来模拟该场景。

代码实现

首先咱们定义观察者Observer:

//================== Observer.h ==================

@interface Observer : NSObject
{
    @protected Subject *_subject;
}

- (instancetype)initWithSubject:(Subject *)subject;

- (void)update;

@end



//================== Observer.m ==================

@implementation Observer

- (instancetype)initWithSubject:(Subject *)subject{
    
    self = [super init];
    if (self) {
        _subject = subject;
       [_subject addObserver:self];
    }
    return self;
}

- (void)update{
    
    NSLog(@"implementation by subclasses");
}
复制代码

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

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

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

//================== Investor.h ==================

@interface Investor : Observer

@end



//================== Investor.m ==================

@implementation Investor

- (void)update{

    float buyingPrice = [_subject getBuyingPrice];
    NSLog(@"investor %p buy stock of price:%.2lf",self,buyingPrice);    
}

@end
复制代码

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

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

目标类Subject

//================== Subject.h ==================

@interface Subject : NSObject
{
    @protected float _buyingPrice;
    @protected NSMutableArray <Observer *>*_observers;
}

- (void)addObserver:(Observer *) observer;


- (void)removeObserver:(Observer *) observer;


- (void)notifyObservers;


- (void)setBuyingPrice:(float)price;


- (double)getBuyingPrice;


@end




//================== Subject.m ==================

@implementation Subject

- (instancetype)init{
    
    self = [super init];
    if (self) {
        _observers = [NSMutableArray array];
    }
    return self;
}


- (void)addObserver:( Observer * ) observer{
    
    [_observers addObject:observer];
}


- (void)removeObserver:( Observer *) observer{
    
    [_observers removeObject:observer];
}


- (void)notifyObservers{
    
    [_observers enumerateObjectsUsingBlock:^(Observer *  _Nonnull observer, NSUInteger idx, BOOL * _Nonnull stop) {
        
        [observer update];
    }];
}


- (void)setBuyingPrice:(float)price{
    
    _buyingPrice = price;
    
    [self notifyObservers];
}


- (double)getBuyingPrice{
    
    return _buyingPrice;
}


@end
复制代码

目标类持有一个可变数组,用来保存观察本身的观察者们;而且还提供了增长,删除观察者的接口,也提供了通知全部观察者的接口。

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

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

OK,如今抽象目标类定义好了,下面咱们看一下具体目标类FinancialAdviser

//================== FinancialAdviser.h ==================

@interface FinancialAdviser : Subject

@end



//================== FinancialAdviser.m ==================

@implementation FinancialAdviser

@end
复制代码

由于全部的接口的事先已经在Subject类定义好了,因此咱们只需新建一个咱们须要的子类便可(若是有不一样于父类的操做的话仍是能够按照本身的方式定义)。

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

FinancialAdviser *fa = [[FinancialAdviser alloc] init];
    
Investor *iv1 = [[Investor alloc] initWithSubject:fa];
    
NSLog(@"====== first advice ========");
[fa setBuyingPrice:1.3];
    
    
Investor *iv2 = [[Investor alloc] initWithSubject:fa];
Investor *iv3 = [[Investor alloc] initWithSubject:fa];

NSLog(@"====== second advice ========");
[fa setBuyingPrice:2.6];
复制代码

从代码中能够看到,咱们最开始向FinancialAdviser(具体目标类)添加了一个具体观察者类的实例iv1,而后FinancialAdviser的实例fa便通知了全部观察者(此时的观察者只有iv1)。

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

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

====== first advice ========
investor 0x600003094c00 buy stock of price:1.30
====== second advice ========
investor 0x600003094c00 buy stock of price:2.60
investor 0x600003083680 buy stock of price:2.60
investor 0x600003083690 buy stock of price:2.60
复制代码

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

代码对应的类图

观察者模式代码示例类图

优势

  • 观察者模式在观察目标和观察者之间创建了一个抽象的耦合。
  • 可实现广播的,一对多的通讯

缺点

  • 若是一个观察目标对象有不少直接和间接的观察者的话,会须要比较多的通讯时间。
  • 须要注意观察者和观察目标之间是否有循环引用。

iOS SDK 和 JDK中的应用

  • 在 iOS SDK 中的 KVO 与 NSNotification 是观察者模式的应用。
  • 在JDK的java.util包中,提供了Observable类以及Observer接口,它们构成了Java语言对观察者模式的支持。

七. 中介者模式

定义

中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互,中介者使各对象之间不须要显式地相互引用,从而使其耦合松散,并且能够独立地改变它们之间的交互。

适用场景

系统结构可能会日益变得复杂,对象之间存在大量的相互关联和调用,系统的总体结构容易变为网状结构。在这种状况下,若是须要修改某一个对象,则可能会要跟踪和该对象关联的其余全部对象,并进行处理。耦合越多,修改的地方就会越多。

若是咱们使用中介者对象,则能够将系统的网状结构变成以中介者为中心的星型结构。中介者承担了中转做用和协调做用,简化了对象之间的交互,并且还能够给对象间的交互进行进一步的控制。

如今咱们清楚了中介者模式的适用场景,下面看一下中介者模式的成员和类图。

成员与类图

成员

中介者模式一共有四个成员:

  1. 抽象中介者(Mediator):抽象中介者定义具体中介者须要实现的接口。
  2. 具体中介者(Concrete Mediator):具体中介者实现抽象中介者定义的接口,承担多个具体同事类之间的中介者的角色。
  3. 抽象同事类(Colleague):抽象同事类定义具体同事类须要实现的接口。
  4. 具体同事类(Concrete Colleague):具体同事类实现抽象同事类定义的接口。

模式类图

状态模式类图

代码示例

场景概述

模拟一个多人对话的场景:当一我的发出消息后,另外的那些人能够收到该消息。

场景分析

假设一共有A,B,C三我的,那么当A发出消息后,须要分别传递给B,C二人。若是三我的直接相互通讯,可能伪代码会是这样的:

A sent message to B
A sent message to C
复制代码

并且随着人数的增多,代码行数也会变多,这显然是不合理的。

所以在这种场景下,咱们须要使用中介者模式,在全部人中间来作一个消息的多路转发:当A发出消息后,由中介者来发送给B和C:

A sent message to Mediator ;
Mediator sent message to B & C
复制代码

下面咱们看一下如何用代码来模拟该场景。

代码实现

首先咱们建立通话的用户类User:

//================== User.h ==================

@interface User : NSObject

- (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator;

- (void)sendMessage:(NSString *)message;

- (void)receivedMessage:(NSString *)message;

@end



//================== User.m ==================

@implementation User
{
    NSString *_name;
    ChatMediator *_chatMediator;
}

- (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator{
    
    self = [super init];
    if (self) {
        _name = name;
        _chatMediator = mediator;
    }
    return self;
}

- (void)sendMessage:(NSString *)message{
    
    NSLog(@"================");
    NSLog(@"%@ sent message:%@",_name,message);
    [_chatMediator sendMessage:message fromUser:self];
    
}

- (void)receivedMessage:(NSString *)message{
    
    NSLog(@"%@ has received message:%@",_name,message);
}

@end
复制代码

用户类在初始化的时候须要传入中介者的实例,并持有。目的是为了在后面发送消息的时候把消息转发给中介者。

另外,用户类还对外提供了发送消息和接收消息的接口。而在发送消息的方法内部其实调用的是中介者的发送消息的方法(由于中介者持有了全部用户的实例,所以能够作多路转发),具体是如何作的咱们能够看下中介者类ChatMediator的实现:

//================== ChatMediator.h ==================

@interface ChatMediator : NSObject

- (void)addUser:(User *)user;

- (void)sendMessage:(NSString *)message fromUser:(User *)user;

@end



//================== ChatMediator.m ==================

@implementation ChatMediator
{
    NSMutableArray <User *>*_userList;
}

- (instancetype)init{
    
    self = [super init];
    
    if (self) {
        _userList = [NSMutableArray array];
    }
    return self;
}

- (void)addUser:(User *)user{

    [_userList addObject:user];
}

- (void)sendMessage:(NSString *)message fromUser:(User *)user{
    
    [_userList enumerateObjectsUsingBlock:^(User * _Nonnull iterUser, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if (iterUser != user) {
            [iterUser receivedMessage:message];
        }
    }];
}

@end
复制代码

中介者类提供了addUser:的方法,所以咱们能够不断将用户添加到这个中介者里面(能够看作是注册行为或是“加入群聊”)。在每次加入一个User实例后,都将这个实例添加到中介者持有的这个可变数组里。因而在未来中介者就能够经过遍历数组的方式来作消息的多路转发,具体实现能够看sendMessage:fromUser:这个方法。

到如今为止,用户类和中介者类都建立好了,咱们看一下消息是如何转发的:

ChatMediator *cm = [[ChatMediator alloc] init];
    
User *user1 = [[User alloc] initWithName:@"Jack" mediator:cm];
User *user2 = [[User alloc] initWithName:@"Bruce" mediator:cm];
User *user3 = [[User alloc] initWithName:@"Lucy" mediator:cm];
    
[cm addUser:user1];
[cm addUser:user2];
[cm addUser:user3];
    
[user1 sendMessage:@"happy"];
[user2 sendMessage:@"new"];
[user3 sendMessage:@"year"];
复制代码

从代码中能够看到,咱们这里建立了三个用户,分别加入到了聊天中介者对象里。再后面咱们分别让每一个用户发送了一条消息。咱们下面经过日至输出来看一下每一个用户的消息接收状况:

[13806:1284059] ================
[13806:1284059] Jack sent message:happy
[13806:1284059] Bruce has received message:happy
[13806:1284059] Lucy has received message:happy
[13806:1284059] ================
[13806:1284059] Bruce sent message:new
[13806:1284059] Jack has received message:new
[13806:1284059] Lucy has received message:new
[13806:1284059] ================
[13806:1284059] Lucy sent message:year
[13806:1284059] Jack has received message:year
[13806:1284059] Bruce has received message:year
复制代码

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

代码对应的类图

中介者模式代码示例类图

优势

  • 中介者使各对象不须要显式地相互引用,从而使其耦合松散。

缺点

  • 在具体中介者类中包含了同事类之间的交互细节,可能会致使具体中介者类很是复杂,使得其难以维护。

iOS SDK 和 JDK中的应用

  • JDK中的Timer就是中介者类的实现,而配合使用的TimerTask则是同事类的实现。

到这里设计模式中的行为型模式就介绍完了,读者能够结合UML类图和demo的代码来理解每一个设计模式的特色和相互之间的区别,但愿读者能够有所收获。

本篇博客的代码和类图都保存在个人GitHub库中:knightsj:object-oriented-design中的 Chapter 2.3

到本篇为止,面向对象设计系列暂时告一段落,短时间内不会有新的文章出来。读者朋友们能够随时给我提意见或沟通。

本篇已同步到我的博客:传送门

该系列前面的三篇文章:

参考书籍和教程

相关文章
相关标签/搜索