python设计模式之观察者模式

有时,我们希望在一个对象的状态改变时更新另外一组对象。在MVC模式中有这样一个非
常常见的例子,假设在两个视图(例如,一个饼图和一个电子表格)中使用同一个模型的数据,
无论何时更改了模型,都需要更新两个视图。这就是观察者设计模式要处理的问题。

观察者模式描述单个对象(发布者,又称为主持者或可观察者)与一个或多个对象(订阅者,
又称为观察者)之间的发布—订阅关系。在MVC例子中,发布者是模型,订阅者是视图。然而,
MVC并非是仅有的发布—订阅例子。信息聚合订阅(比如, RSS或Atom)是另一种例子。许多读
者通常会使用一个信息聚合阅读器订阅信息流,每当增加一条新信息时,他们就能自动地获取到
更新。

观察者模式背后的思想等同于MVC和关注点分离原则背后的思想,即降低发布者与订阅者
之间的耦合度,从而易于在运行时添加/删除订阅者。此外,发布者不关心它的订阅者是谁。它
只是将通知发送给所有订阅者。

1. 现实生活的例子

现实中,拍卖会类似于观察者模式。每个拍卖出价人都有一些拍牌,在他们想出价时就可以举起来。不论出价人在何时举起一块拍牌,拍卖师都会像主持者那样更新报价,并将新的价格广播给所有出价人(订阅者)。

2. 软件的例子

django-observer源代码包是一个第三方Django包,可用于注册回调函数,之后在某些Django模型字段发生变化时执行。它支持许多不同类型的模型字段( CharField、 IntegerField等)。

RabbitMQ可用于为应用添加异步消息支持,支持多种消息协议(比如, HTTP和AMQP),可
在Python应用中用于实现发布—订阅模式,也就是观察者设计模式。

3. 应用案例

当我们希望在一个对象(主持者/发布者/可观察者)发生变化时通知/更新另一个或多个对象的时候,通常会使用观察者模式。观察者的数量以及谁是观察者可能会有所不同,也可以(在运行时)动态地改变。

可以想到许多观察者模式在其中有用武之地的案例。本章开头已提过这样的一个案例,就是信息聚合。无论格式为RSS、 Atom还是其他,思想都一样:你追随某个信息源,当它每次更新时,你都会收到关于更新的一个通知。

同样的概念也存在于社交网络。如果你使用社交网络服务关联了另一个人,在关联的人更新某些内容时,你能收到相关通知,不论这个关联的人是你关注的一个Twitter用户, Facebook上的一个真实朋友,还是LinkdIn上的一位同事。

事件驱动系统是另一个可以使用(通常也会使用)观察者模式的例子。在这种系统中,监听者被用于监听特定事件。监听者正在监听的事件被创建出来时,就会触发它们。这个事件可以是键入(键盘的)某个特定键、移动鼠标或者其他。事件扮演发布者的角色,监听者则扮演观察者的角色。在这里,关键点是单个事件(发布者)可以关联多个监听者(观察者)。

4. 实现

我们将实现一个数据格式化程序,默认格式化程序是以十进制格式展示一个数值,然而,我们可以添加/注册更多的格式化程序。这个例子中将添加一个十六进制格式化程序和一个二进制格式化程序。每次更新默认格式化程序的值时,已注册的格式化程序就会收到通知,并采取行动。在这里,行动就是以相关的格式展示新的值。

在一些模式中,继承能体现自身价值,观察者模式是这些模式中的一个。我们可以实现一个基类Publisher,包括添加、删除及通知观察者这些公用功能。 DefaultFormatter类继承自Publisher,并添加格式化程序特定的功能。我们可以按需动态地添加删除观察者。下面的类图展示了一个使用两个观察者( HexFormatter和BinaryFormatter)的示例。注意,因为类图是静态的,所以无法展示系统的整个生命周期,只能展示某个特定时间点的系统状态。

从Publisher类开始说起。观察者们保存在列表observers中。 add()方法注册一个新的观察者,或者在该观察者已存在时引发一个错误。 remove()方法注销一个已有观察者,或者在该观察者尚未存在时引发一个错误。最后, notify()方法则在变化发生时通知所有观察者。

class Publisher: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):[o.notify(self) for o in self.observers]

接着是DefaultFormatter类。 init()做的第一件事情就是调用基类的__init__()方法,因为这在Python中没法自动完成。 DefaultFormatter实例有自己的名字,这样便于我们跟踪其状态。对于data变量,我们使用了名称改编来声明不能直接访问该变量。注意, Python中直接访问一个变量始终是可能的,不过资深开发人员没有借口这样做,因为代码已经声明不应该这样做。这里使用名称改编是有一个严肃理由的。请继续往下看。DefaultFormatter把_data变量用作一个整数,默认值为零。

class DefaultFormatter(Publisher):def __init__(self, name):Publisher.__init__(self)self.name = nameself._data = 0

str()方法返回关于发布者名称和_data值的信息。 type(self).__name是一种获取类名的方便技巧,避免硬编码类名。这降低了代码的可读性,却提高了可维护性。是否喜欢,要看你的选择。

    def __str__(self):return "{}: '{}' has data = {}".format(type(self).__name__, self.name, self._data)

类中有两个data()方法。第一个使用@property修饰器来提供_data变量的读访问方式。这样,我们就能使用object.data来替代object.data()。

 @propertydef data(self):return self._data

第二个data()更有意思。它使用了@setter修饰器,该修饰器会在每次使用赋值操作符( =)为_data变量赋新值时被调用。该方法也会尝试把新值强制类型转换为一个整数,并在类型转换失败时处理异常。

    @data.setterdef data(self, new_value):try:self._data = int(new_value)except ValueError as e:print('Error: {}'.format(e))else:self.notify()

下一步是添加观察者。 HexFormatter和BinaryFormatter的功能非常相似。唯一的不同在于如何格式化从发布者那获取到的数据值,即分别以十六进制和二进制进行格式化。

class HexFormatter:def notify(self, publisher):print("{}: '{}' has now hex data = {}".format(type(self).__name__,publisher.name, hex(publisher.data)))
class BinaryFormatter:def notify(self, publisher):print("{}: '{}' has now bin data = {}".format(type(self).__name__,publisher.name, bin(publisher.data)))

如果没有测试数据,示例就不好玩了。 main()函数一开始创建一个名为test1的DefaultFormatter实例,并在之后关联了两个可用的观察者。也使用了异常处理来确保在用户输入问题数据时应用不会崩溃。此外,诸如两次添加相同的观察者或删除尚不存在的观察者之类的事情也不应该导致崩溃。

def main():df = DefaultFormatter('test1')print(df)print()hf = HexFormatter()df.add(hf)df.data = 3print(df)print()bf = BinaryFormatter()df.add(bf)df.data = 21print(df)print()df.remove(hf)df.data = 40print(df)print()df.remove(hf)df.add(bf)df.data = 'hello'print(df)print()df.data = 15.8print(df)

完整代码如下:

class Publisher: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):[o.notify(self) for o in self.observers]
class DefaultFormatter(Publisher):def __init__(self, name):Publisher.__init__(self)self.name = nameself._data = 0def __str__(self):return "{}: '{}' has data = {}".format(type(self).__name__, self.name,self._data)@propertydef data(self):return self._data@data.setterdef data(self, new_value):try:self._data = int(new_value)except ValueError as e:print('Error: {}'.format(e))else:self.notify()
class HexFormatter:def notify(self, publisher):print("{}: '{}' has now hex data = {}".format(type(self).__name__,publisher.name, hex(publisher.data)))
class BinaryFormatter:def notify(self, publisher):print("{}: '{}' has now bin data = {}".format(type(self).__name__,publisher.name, bin(publisher.data)))
def main():df = DefaultFormatter('test1')print(df)print()hf = HexFormatter()df.add(hf)df.data = 3print(df)print()bf = BinaryFormatter()df.add(bf)df.data = 21print(df)print()df.remove(hf)df.data = 40print(df)print()df.remove(hf)df.add(bf)df.data = 'hello'print(df)print()df.data = 15.8print(df)
if __name__ == '__main__':main()

输出如下:

DefaultFormatter: 'test1' has data = 0
HexFormatter: 'test1' has now hex data = 0x3
DefaultFormatter: 'test1' has data = 3
HexFormatter: 'test1' has now hex data = 0x15
BinaryFormatter: 'test1' has now bin data = 0b10101
DefaultFormatter: 'test1' has data = 21
BinaryFormatter: 'test1' has now bin data = 0b101000
DefaultFormatter: 'test1' has data = 40
Failed to remove: <__main__.HexFormatter object at 0x7f30a2fb82e8>
Failed to add: <__main__.BinaryFormatter object at 0x7f30a2fb8320>
Error: invalid literal for int() with base 10: 'hello'
BinaryFormatter: 'test1' has now bin data = 0b101000
DefaultFormatter: 'test1' has data = 40
BinaryFormatter: 'test1' has now bin data = 0b1111
DefaultFormatter: 'test1' has data = 15

在输出中我们看到,添加额外的观察者,就会出现更多(相关的)输出;一个观察者被删除后,就再也不会被通知到。这正是我们想要的,能够按需启用/禁用运行时通知。

应用的防护性编程方面看起来也工作得不错。尝试玩一些花样都是不会被允许的,比如,删除一个不存在的观察者或者两次添加相同的观察者。不过,显示的信息还不太友好,就留给你作为练习吧。在API要求一个数字参数时输出一个字符串所导致的运行时失败,也能得到正确处理,不会造成应用崩溃/终止。

5. 小结

若希望在一个对象的状态变化时能够通知/提醒所有相关者(一个对象或一组对象),则可以使用观察者模式。观察者模式的一个重要特性是,在运行时,订阅者/观察者的数量以及观察者是谁可能会变化,也可以改变。

为理解观察者模式,你可以想一想拍卖会的场景,出价人是订阅者,拍卖师是发布者。这一模式在软件领域的应用非常多。

python设计模式之模板模式相关推荐

  1. Python设计模式:模板模式

    设计模式十七:模板模式 什么是模板模式 编写优秀代码的一个要素是避免冗余: 模式关注的是消除代码冗余: 无需改变算法结构就能重新定义一个算法的某些部分. 使用场景 发现结构相近的算法中有重复代码,则可 ...

  2. Python设计模式-装饰器模式

    Python设计模式-装饰器模式 代码基于3.5.2,代码如下; #coding:utf-8 #装饰器模式class Beverage():name = ""price = 0.0 ...

  3. Python设计模式-中介者模式

    Python设计模式-中介者模式 代码基于3.5.2,代码如下; #coding:utf-8 #中介者模式class colleague():mediator = Nonedef __init__(s ...

  4. Python设计模式-职责链模式

    Python设计模式-职责链模式 代码基于3.5.2,代码如下; #coding:utf-8 #职责链模式class Handler():def __init__(self):self.success ...

  5. Python设计模式-享元模式

    Python设计模式-享元模式 基于Python3.5.2,代码如下 #coding:utf-8class Coffee:name = ""price = 0def __init_ ...

  6. python的编程模式-Python设计模式之状态模式原理与用法详解

    本文实例讲述了Python设计模式之状态模式原理与用法.分享给大家供大家参考,具体如下: 状态模式(State Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类 ...

  7. python策略模式包含角色_详解Python设计模式之策略模式

    虽然设计模式与语言无关,但这并不意味着每一个模式都能在每一门语言中使用.<设计模式:可复用面向对象软件的基础>一书中有 23 个模式,其中有 16 个在动态语言中"不见了,或者简 ...

  8. 设计模式回顾——模板模式(C++)

    文章目录 1 前言 2 什么是模板模式 2.1 模板模式组成 2.2 模板模式UML图 2.3 模板模式作用 3 模板模式优缺点 4 什么地方使用模板模式 5 模板模式实现 6 模板模式与策略模式比较 ...

  9. python设计模式之建造者模式

    python设计模式之建造者模式 ​ 建造者模式的适用范围:想要创建一个由多个部分组成的对象,而且它的构成需要一步接一步的完成.只有当各个部分都完成了,这个对象才完整.建造者模式表现为复杂对象的创建与 ...

最新文章

  1. win7 登录界面---管理员账户禁用与显示
  2. oracle 12519,TNS-12519 与 processes 参数设置
  3. 使用ISA实现用户级验证(1~3篇)
  4. JS中的兼容问题总结
  5. mysqld_safe启动mysql
  6. java中 怎么获取bean_java普通类如何得到spring中的bean类
  7. 关于批量插入数据之我见(100万级别的数据,mysql)
  8. android电话系统,Android电话系统之-rild.doc
  9. 《大数据》专题征文:国产环境下的大数据处理系统
  10. jQuery-动画与特效
  11. mysql server8 jdbc_mysql8.0 jdbc连接注意事项
  12. vfifo控制mig_virtual fifo的使用
  13. 原生mysql 怎么创表_Mysql的基础使用之SQL原生语句的使用:表的 创建 删除 修改 (一)...
  14. Linux内核线程kernel thread详解--Linux进程的管理与调度(十)
  15. 超详细SPSS主成分分析计算指标权重(二:权重计算及极差法标准化)
  16. 软件测试的发展前景怎么样 做软件测试有前途吗
  17. 2013年IT界25个最古怪的面试题
  18. 机器算法基础——回归分析
  19. Shake Shack上海第7家门店开业
  20. 高通linux-多核启动以及CPU热插拔驱动

热门文章

  1. 服务器采购框架合同协议书范本,服务器采购框架合同协议书范本(12页)-原创力文档...
  2. 契约式编程与防御式编程
  3. Metalink Account
  4. 转:Metalink 账户
  5. 老杜(杜昶旭)GRE填空笔记部分整理-by“ 1哥”+ TTC相关资料
  6. Lammps之MP方法粘度计算(包含in文件)
  7. 【L2-020 功夫传人】天梯赛L2系列详解
  8. 2022年合束器市场前景分析及研究报告
  9. 使用高德api的详细步骤
  10. 雪佛兰linux高德_高德地图的使用一