模式定义:
观察者模式 实现了 发布-订阅 机制,在一个对象发生改变时,主动通知其他 一个或多个对象进行对应操作;
生活中的例子
- 店铺上新款,发送邮件给用户; 店铺上新款 是 发布者, 订阅者是 用户;
- 血汗工厂 下班时 的铃声,通知 所有员工 下班; 铃声 是 发布者, 订阅者是 所有员工;
代码中的例子(何时该使用此模式):
- 基于AMQP协议实现的 rabbitmq 的 广播模式;一个消息发布者 对应多个消息消费者
生活中演化步骤
手机出新品时,用户想去 当地手机体验店 体验一下新机,但是 最近几天货还没到, 怎么办呢?
1.用户可以 每天去体验店 问一下到货了吗,这样来回去了10天 才到货,人也瘦了10斤; 缺点:用户轮询,浪费资源
2.用户可以 把自己手机号给体验店员工,当到货时 给你发短信; 优点:用户 异步非阻塞(用户可以做其他事情,等发短信时再去体验新机),可以通过此 观察者模式实现;
该模式关键的角色:
- 抽象发布者角色:定义子类的公共 注册,删除 观察者的方法,及通知所有观察者的方法;
- 具体发布者角色:实现 注册,删除,通知 观察者的具体功能;
- 抽象订阅者角色:定义子类的 功能方法;注意此方法名称 所有子类必须相同;
- 具体订阅者角色:实现 具体的功能方法;此方法 在具体订阅者角色的所有类中必须相同;
该模式的主要优缺点如下:
优点:
- 开闭原则; 无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)
- 降低了 发布者和订阅者之间,订阅者 彼此之间 的耦合;
- 发布者和订阅者之间建立了一种 触发机制;发布者主动触发订阅者, 这种异步非阻塞(回调) 的 方式效率 相对于 同步非阻塞(轮询) 的 方式高很多;
缺点:
- 发布者 通知所有订阅者时 由于订阅者被注册在list中,所以限制了 并发性,且强制增加了 订阅者间的执行顺序; 可以根据实际情况 在通知订阅者时,使用 多进程或多线程 摆脱此缺点;
和 其他模式 的 比较:
责任链模式 比较:
相同点:
责任链模式中的责任者,观察者模式中的订阅者 在处理请求时, 客户端都可以定义 处理顺序; 因为 观察者模式中,发布者用list存储订阅者;
不同点:
责任链模式 在客户端设置完请求顺序后,无法修改; 而 观察者模式 中的 观察者可以动态的 添加或删除订阅者;
在一些需求 如 一个请求 只需要按照一定的顺序 让每个处理者 处理时, 这2种模式都可以实现(但是最好还是用 责任者模式,因为 发布订阅模式 订阅者最好只接收来自发布者的原始消息,而不是其他订阅者处理完修改后的消息),这时模式的区别只在于 责任链模式中 定义下一个处理者的位置在上个处理者, 观察者模式中 定义订阅者 在观察者角色 list类型的类变量中;
示例代码部分
# -*- coding: utf-8 -*-
"""
(C) rgc
All rights reserved
create time '2020/12/10'
Usage:
观察者模式 实现示例
一个应用 实现 股票涨停 时, 邮件和短信通知 用户
这时 股票涨停 是事件发布者,事件订阅者 是 邮件通知功能 和 短信通知功能
"""
import random
class BasePublisher(object):
"""
"""
def add(self, subscriber):
"""
:param subscriber:
:return:
"""
pass
def cancel(self, subscriber):
"""
:param subscriber:
:return:
"""
pass
def notify(self, context):
"""
:param context:
:return:
"""
pass
class Publisher(BasePublisher):
"""
"""
# 订阅者list
subscriber_list = []
def add(self, subscriber):
"""
:param subscriber:
:return:
"""
self.subscriber_list.append(subscriber)
def cancel(self, subscriber):
"""
:param subscriber:
:return:
"""
self.subscriber_list.remove(subscriber)
def notify(self, context):
"""
:param context:
:return:
"""
for item in self.subscriber_list:
item.send(context)
def check_trading_limit(self, user_name):
"""
检查是否涨停
此处用 随机数表示是否涨停
:param user_name:
:return:
"""
# 1表示涨停
if random.randint(0, 1):
self.notify(user_name)
else:
print('股票没有涨停,不发通知')
class BaseSubscriber(object):
"""
"""
def send(self, name):
"""
:return:
"""
pass
class EmailSubscriber(BaseSubscriber):
"""
邮件订阅者角色 类
"""
def send(self, name):
"""
:return:
"""
print(f'hello,{name},现在发送邮件通知您 的股票涨停了!!!')
class SmsSubscriber(BaseSubscriber):
"""
短信订阅者角色 类
"""
def send(self, name):
"""
:return:
"""
print(f'hello,{name},现在发送短信通知您 的股票涨停了!!!')
if __name__ == '__main__':
# 初始化 发布者,订阅者 对象
pub = Publisher()
email_sub = EmailSubscriber()
sms_sub = SmsSubscriber()
# 发布者 添加订阅者
pub.add(email_sub)
pub.add(sms_sub)
# 发布者 触发事件
pub.check_trading_limit('Lucy')
pub.check_trading_limit('Tom')
# 发布者 删除 订阅者
pub.cancel(email_sub)
print('*' * 10)
pub.check_trading_limit('Jerry')
pub.check_trading_limit('Bob')
示例运行结果:
总结:
观察者模式 一定程度实现了 异步非阻塞的 功能,通过触发式 的 执行任务,而不是轮询 从而节约资源;
观察者模式 和 责任链模式 有一定程度的相似性,如果不考虑彼此不同点,可以都试试;
但是 观察者侧重于 发布订阅消息,每个订阅者接收的消息应该相同,且处理消息时应该无序; 责任链 注重任务处理顺序的灵活配置;
相关链接: