行为型模式之观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern),比如,当一个对象呗修改时,则会自动通知它的依赖对象,观察者模式属于行为型模式

一个经典的例子就是订阅报纸.你不用去任何地方,只需要将你的个人地址信息以及订阅信息告诉出版社,出版社就知道如何将相关报纸传递给你.这种模式的第二个名称叫做发布/订阅模式

观察者模式的思想非常简单,subject(主题)允许别的对象-观察者(这些对象实现了观察者接口)对这个subject的改变进行订阅和取消订阅.当subject发生了改变-那么subject会将这个变化发送给所有的观察者,观察者就能对subject的变化做出更新

简单的说就是,当某对象改变时,自动通知所有相关的状态进行更新。

在这里插入图片描述

模式结构和说明

在这里插入图片描述

Subject:目标对象,通常具有如下功能:

  1. 一个目标可以被多个观察者观察
  2. 目标提供对观察者注册和退订的维护
  3. 当目标的状态发生变化时,目标负责通知所有注册的,有效的观察者
    Observer:定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,可以在这个方法里面回调目标对象,已获取目标对象的数据
    ConcreteSubject:具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知所有注册有效的观察者,让观察者执行相应的处理
    ConcreteObserver:观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,比如更新自身的状态以保持和目标的相应状态一致

示例代码

  1. 定义订阅类(观察者类),包含创建订阅,添加客户等
@interface SubscribeCenter : NSObject
#pragma mark - 订阅号
/**
 创建订阅号

 @param subscription 订阅号名称
 */
+ (void)createSubscription:(NSString *)subscription;

/**
 移除订阅号

 @param subscription 订阅号名称
 */
+ (void)removeSubscription:(NSString *)subscription;

#pragma mark - 客户
/**
 添加客户到订阅号中(可以直接添加客户,默认会创建订阅号)

 @param customer 客户
 @param subscription 订阅号名称
 */
+ (void)addCustomer:(id<SubscribeCenterProtocol>)customer subscription:(NSString *)subscription;
/**
 移除客户
 
 @param customer 客户
 @param subscription 订阅号名称
 */
+ (void)removeCustomer:(id<SubscribeCenterProtocol>)customer
      withSubscription:(NSString *)subscription;

#pragma mark - 发送消息
/**
 发送消息到具体的订阅号

 @param message 消息
 @param subscription 订阅号名称
 */
+ (void)sendMessage:(id)message toSubscription:(NSString *)subscription;

@end

static NSMutableDictionary *_subscribeDic = nil;
@implementation SubscribeCenter
+ (void)initialize
{
    if (self == [SubscribeCenter class]) {
        _subscribeDic = [NSMutableDictionary dictionary];
    }
}

#pragma mark - 订阅号
+ (void)createSubscription:(NSString *)subscription
{
    NSParameterAssert(subscription);
    [self existSubscription:subscription];
}

+ (void)removeSubscription:(NSString *)subscription
{
    NSParameterAssert(subscription);
    NSHashTable *hashTable = _subscribeDic[subscription];
    if (hashTable) {
        [_subscribeDic removeObjectForKey:subscription];
    }
}
#pragma mark - 客户
+ (void)addCustomer:(id<SubscribeCenterProtocol>)customer subscription:(NSString *)subscription
{
    NSParameterAssert(customer);
    NSParameterAssert(subscription);
    NSHashTable *hashTable = [self existSubscription:subscription];
    [hashTable addObject:customer];
}

+ (void)removeCustomer:(id<SubscribeCenterProtocol>)customer
      withSubscription:(NSString *)subscription
{
    NSParameterAssert(subscription);
    NSHashTable *hashTable = _subscribeDic[subscription];
    [hashTable removeObject:customer];
    
}
#pragma mark - 发送消息
+ (void)sendMessage:(id)message toSubscription:(NSString *)subscription
{
    NSParameterAssert(subscription);
    NSHashTable *hashTable = _subscribeDic[subscription];
    if (hashTable) {
        NSLog(@"hashtable %@",hashTable);
        NSEnumerator *enumerator = [hashTable objectEnumerator];
        id<SubscribeCenterProtocol> object = nil;
        while (object = [enumerator nextObject]) {
            if ([object respondsToSelector:@selector(subscribeMessage:subscription:)]) {
                [object subscribeMessage:message subscription:subscription];
            }
        }
    }
}


#pragma - 私有方法
+ (NSHashTable *)existSubscription:(NSString *)subscription
{
    NSHashTable *hashTable = [_subscribeDic objectForKey:subscription];
    if (hashTable == nil) {
        hashTable = [NSHashTable weakObjectsHashTable];
        _subscribeDic[subscription] = hashTable;
    }
    return hashTable;
}

@end

这里保存客户使用的是NSHashTable,有人会问为什么不用NSArray呢?
因为NSHashTable会对添加进的对象持有弱引用,当添加进来的元素,外部对他持有的强引用都取消的时候,这个对象就会自动从内存中消除,这样就就不需要手动移除这些对象.这个相比KVO和通知,就不需要我们手动去操作实现removeObserve方法
subscription:相当于Notification的name

  1. 定义一个观察者接受到消息的统一处理接口
@protocol SubscribeCenterProtocol <NSObject>

@required
- (void)subscribeMessage:(id)message subscription:(NSString *)subscription;

@end

所有的观察者都应该遵循这个协议
3. 定义观察者类

@implementation Animal

- (void)subscribeMessage:(id)message subscription:(NSString *)subscription
{
    NSLog(@"animal %@",message);
}
@end
  1. 客户端实现
static NSString *GTSubscrip = @"GTSubscrip";
- (void)viewDidLoad {
    [super viewDidLoad];
    Animal *animal = [Animal new];
    [SubscribeCenter addCustomer:self subscription:GTSubscrip];
    [SubscribeCenter addCustomer:animal subscription:GTSubscrip];
    [SubscribeCenter sendMessage:@"消息-订阅" toSubscription:GTSubscrip];
}

- (void)subscribeMessage:(id)message subscription:(NSString *)subscription
{
    NSLog(@"controller %@",message);
}
@end

在这里插入图片描述
从上图看出,subcrib发送消息,所有的观察者都接受到消息,并作出处理

iOS中的观察者模式

在iOS中观察者模式的实现有四种方法:NSNotification、KVO、Protocol以及Code Block代码块。

  1. Notification是一对多的,而delegate回调是一对一的。但是你可以做一个Array来实现对多回调,这个问题的其实意义不大。
  2. Notification - NotificationCenter机制使用了操作系统的对象间通讯功能,而delegate是直接的函数调用。Notification跨度大,而delegate效率可能比较高。
  3. 相较于前两者KVO才是一种真正的观察者模式,它允许你将一个处理函数绑定到某个类的属性,属性发生改变是就会自动触发,不像其他两种需要你手动的发通知。KVO是一种非常灵活的观察机制,广泛应用于界面设计
  4. Code Block其实就相当于C的函数指针,可以用来做各种回调。我觉得其应当具备最高的效率。使用Code Block要注意的地方就是使用外部变量。在block里直接引用外部变量的话会在block定义的时候复制外部变量的一个拷贝,也就是说得到的是block定义时的值,在block内修改这个值也不会传给外部。要得到实时的数据,或者将数据传出的话需要在相关变量前面加__block即可

具体使用就不讲解了

模式讲解

1. 目标和观察者之间的关系

按照模式的定义,目标和观察者之间的典型的一对多的关系,但是如果观察者只有一个也是可以的,这样就变相实现了目标和观察者之间一对一的关系,这也使得在处理一个对象的状态变化会影响到另一个对象的时候,也可以考虑使用观察者模式

同样的,一个观察者也可以观察多个目标,如果观察者为多个目标定义的通知更新方法都是update方法的话,这会带来麻烦,因为需要接收多个目标的通知,如果是一个update的方法,那就需要在方法内部区分,到底这个更新的通知来自于哪一个目标,不同的目标有不同的后续操作。
一般情况下,观察者应该为不同的观察者目标,定义不同的回调方法,这样实现最简单,不需要在update方法内部进行区分。就像上面的协议方法似的,多目标协议方法尽可能不要相同

2. 单向依赖

在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而目标是不会依赖于观察者的。
它们之间联系的主动权掌握在目标手中,只有目标知道什么时候需要通知观察者,在整个过程中,观察者始终是被动的,被动的等待目标的通知,等待目标传值给它。
对目标而言,所有的观察者都是一样的,目标会一视同仁的对待。

3.基本的实现说明

  1. 具体的目标实现对象要能维护观察者的注册信息
  2. 具体的目标实现对象需要维护引起通知的状态
  3. 具体的观察者实现对象需要能接收目标的通知,能够接收目标传递的数据,或者是能够主动去获取目标的数据,并进行后续处理。

模式本质

观察者模式的本质:触发联动。
当修改目标对象的状态的时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法,其实就相当于联动调用这些观察者的方法。而且这个联动还是动态的,可以通过注册和取消注册来控制观察者,因而可以在程序运行期间,通过动态的控制观察者,来变相的实现添加和删除某些功能处理.
同时目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确地联动过来。

Demo地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值