什么是观察者模式?
观察者模式 也可以称之为 发布者–订阅者模式,描述单个对象(发布者)与一个或多个对象(订阅者)之间的 发布—订阅 关系。当 发布者 发生变化时,会自动以广播的方式通知给每一个订阅者。模型如下图所示:
在MVC例子中,发布者是模型,订阅者是视图。
在信息聚合订阅(比如, RSS)中,许多读者通常会使用一个信息聚合阅读器订阅信息流,每当增加一条新信息时,他们就能自动地获取更新,这个过程也是发布者–订阅者模型。
观察者模式的使用场景
- 当我们希望一个对象发送变化时,能自动让其他对象知道的时候,我们就可以使用观察者模式;
- 在社交网络上的应用:当你在社交网络上关联某个人时,只有关联的人更新了某些内容,你就能收到相关通知;
- 在事件驱动中的应用:在这种系统中,监听者 被用于监听特定事件。监听者 正在监听的事件被创建出来时,就会触发它们。这个事件可以是键入(键盘的)某个特定键、移动鼠标等。事件扮演发布者的角色,监听者则扮演观察者的角色。在这里,单个事件(发布者)可以关联多个监听者(观察者)。
观察者模式的优点
观察者模式 背后的思想是:降低 发布者 与 订阅者 之间的 耦合度。从而具有以下优点:
- 易于在运行时添加和删除 订阅者;
- 发布者 不关心 订阅者 是谁,只负责发布消息;
观察者模式的例子
实现一个数据格式化的程序。默认格式化程序是以十进制格式展示一个数值。然而,我们可以添加/注册更多的格式化程序。这个例子中将添加一个十六进制格式化程序和一个二进制格式化程序。每次更新默认格式化程序的值时,已注册的格式化程序就会收到通知,并以相关的格式展示数据。
类间的关系如下所示:
- Publisher 为基类:
self.obervers
:保存 订阅者 实例;self.add(self, observer)
:添加 订阅者;self.remove(self, observer)
:删除 订阅者;self.notify(self)
:通知 订阅者。
- DefaultFormatter 为继承类:
self.name
:记录实例名称;self._data
:保存数据;
-self.data(self)
:方便以属性的方式调用该函数;self.data(self, new_value)
:方便以左赋值的方式调用该函数;
- HexFormatter 和 BinaryFormatter 为 订阅者类:
self._data
:存储数据;self.subscribe(self, publisher)
:模拟订阅发布者
实现发布者类
#!/bin/bash
# 发布者
class Publisher(object):
def __init__(self):
self.observers = [] # 观察者列表
# 添加观察者
def add(self, observer):
if observer not in self.observers:
self.observers.append(observer)
else:
print('Failed to add: {}'.format(observer))
# 移除观察者
def remove(self, observer):
try:
self.observers.remove(observer)
except ValueError:
print('Failed to remove: {}'.format(observer))
# 通知观察者
def notify(self):
# 逐个通知观察者数据发生变化
[observer.subscribe(self) for observer in self.observers]
# 改进的发布者
class DefaultFormatter(Publisher):
def __init__(self, name):
"""
Params:
- name: 指定类实例名称
"""
super().__init__()
self.name = name # 设置实例名称,方便跟踪其状态
self._data = 0 # 设置私有变量
def __str__(self):
"""
Func:
使用 printf(类实例)会调用此函数
"""
return "{}: '{}' has data = {}".format(type(self).__name__, self.name, self._data)
@property
def data(self):
return self._data
@data.setter
def data(self, new_value):
"""
Func:
发生赋值操作时会调用此函数,例如:
dfTest = DefaultFormatter("dfTest")
dfTest.data = 3 # 等效 dfTest.data(new_value = 3)
"""
try:
self._data = int(new_value)
except ValueError as e:
print('Error: {}'.format(e))
else:
self.notify()
上述代码中:
type(self).__name
是一种获取类名的方便技巧,避免硬编码类名。这降低了代码的可读性,却提高了可维护性;@property修饰器
的作用:可以像访问属性一样访问变量,例如:object.data
来代替object.data()
;
@property
def data(self):
return self._data
- 使用
@setter
修饰器:该修饰器会在每次使用赋值操作符(=
)为_data
变量赋新值时被调用;同时赋新值之时,会向订阅者发送信息。
@data.setter
def data(self, new_value):
"""
Func:
发生赋值操作时会调用此函数,例如:
dfTest = DefaultFormatter("dfTest")
dfTest.data = 3 # 等效 dfTest.data(new_value = 3)
"""
try:
self._data = int(new_value)
except ValueError as e:
print('Error: {}'.format(e))
else:
self.notify()
添加观察者
HexFormatter
和 BinaryFormatter
的功能非常相似。唯一的不同在于如何格式化从发布者那获取到的数据值,即分别以十六进制和二进制进行格式化。
# 十六进制观察者
class HexFormatter:
def __init__(self):
self._data = hex(0)
def subscribe(self, publisher):
"""
Params:
- publisher: 指向实例对象的指针
"""
self._data = hex(publisher._data)
print("{}: '{}' has now hex data = {}".format(type(self).__name__, publisher.name, self._data))
# 二进制观察者
class BinaryFormatter:
def __init__(self):
self._data = bin(0)
def subscribe(self, publisher):
"""
Params:
- publisher: 指向实例对象的指针
"""
self._data = bin(publisher._data)
print("{}: '{}' has now bin data = {}".format(type(self).__name__, publisher.name, self._data))
应用上述代码
def main():
# 申请一个发布者
PublisherTest = DefaultFormatter('PublisherTest')
print(PublisherTest)
print()
print("添加十六进制格式订阅者")
hf = HexFormatter() # 申请一个订阅者
PublisherTest.add(hf) # 向发布者添加订阅者
PublisherTest.data = 3 # 等效为PublisherTest.data(new_value = 3)
print(PublisherTest)
print()
print("添加二进制格式订阅者")
bf = BinaryFormatter() # 申请一个订阅者
PublisherTest.add(bf) # 在发布者中添加订阅者
PublisherTest.data = 21 # 等效为PublisherTest.data(new_value = 21)
print(PublisherTest) # 打印发布者信息
print()
print("移除十六进制格式订阅者")
PublisherTest.remove(hf) # 移除一个订阅者
PublisherTest.data = 40 # 等效为PublisherTest.data(new_value = 40)
print(PublisherTest) # 打印发布者的信息
print()
print("移除十六进制格式订阅者、添加二进制格式订阅者")
PublisherTest.remove(hf) # 移除一个订阅者
PublisherTest.add(bf) # 添加一个订阅者
PublisherTest.data = 'hello' # 等效为PublisherTest.data(new_value = 'hello')
print(PublisherTest) # 打印订阅者信息
print()
PublisherTest.data = 15.8 # 等效为PublisherTest.data(new_value = 15.8)
print(PublisherTest) # 打印订阅者信息
if __name__ == '__main__':
main()
结果如下:
DefaultFormatter: 'PublisherTest' has data = 0
添加十六进制格式订阅者
HexFormatter: 'PublisherTest' has now hex data = 0x3
DefaultFormatter: 'PublisherTest' has data = 3
添加二进制格式订阅者
HexFormatter: 'PublisherTest' has now hex data = 0x15
BinaryFormatter: 'PublisherTest' has now bin data = 0b10101
DefaultFormatter: 'PublisherTest' has data = 21
移除十六进制格式订阅者
BinaryFormatter: 'PublisherTest' has now bin data = 0b101000
DefaultFormatter: 'PublisherTest' has data = 40
移除十六进制格式订阅者、添加二进制格式订阅者
Failed to remove: <__main__.HexFormatter object at 0x7fd6fe67fe80>
Failed to add: <__main__.BinaryFormatter object at 0x7fd6fe67fef0>
Error: invalid literal for int() with base 10: 'hello'
DefaultFormatter: 'PublisherTest' has data = 40
BinaryFormatter: 'PublisherTest' has now bin data = 0b1111
DefaultFormatter: 'PublisherTest' has data = 15
完成的代码在这里
参考资料
- 《精通Python设计模式》