热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python设计模式观察者模式

题目:现在你有一个数字,默认格式化程序是以十进制格式展示此数值,但需要提供一个功能,这个程序要支持添加注册更多的格式化程序&
题目:现在你有一个数字,默认格式化程序是以十进制格式展示此数值,但需要提供一个功能,这个程序要支持添加/注册更多的格式化程序(比如:添加一个十六进制格式化程序和一个二进制格式化程序)。每次数值更新时,已注册的程序就会收到通知,并显示更新后的值。

我们看下需求:

  1. NumberFormatter 有一个 number 属性
  2. 当 number 值修改时,相关的格式化方式展示结果要改变
  3. 此系统必须可扩展已适应其他格式化方式的使用。

一个错误的实现可能是这样的:

class NumberFormatter(object):def __init__(self, number):self.number = numberdef show_data(self):self.default_formatter()self.hex_formatter()self.binary_formatter()def default_formatter(self):passdef hex_formatter(self):passdef binary_formatter(self):pass

我们可以这么使用:

number = NumberFormatter(10)
number.show_data()

但是这样会有一个问题:这种针对实现的编程会导致我们在增加或者删除需要格式化方式时必须修改代码。比如我们现在不再需要十六进制数字格式的显示,就需要把 hex_formatter 相关的代码删除或者注释掉。

要解决这个问题,就可以用到我们这次要介绍的观察者模式了。

什么是观察者模式

认识观察者模式

我们先看看报纸和杂志的订阅是怎么回事:

  1. 报社的业务就是出版报纸
  2. 向某家报社订阅报纸,只要他们有新报纸,就会给你送来,只要你是他们的订户,你就会一直受到新报纸。
  3. 当你不再想看的时候,取消订阅,他们就不会在送新报纸给你
  4. 只要报社还在运营,就会一直有人向他们订阅报纸或取消订阅。

我们用图表示一下,这里出版者 改称为主题(Subject),订阅者改称为观察者(Observer):

1. 开始的时候,鸭子对象不是观察者

2. 鸭子对象过来告诉主题,它想当一个观察者(鸭子其实想说的是:我对你的数据改变感兴趣,一有变化请通知我)

3. 鸭子对象已经是观察者了(鸭子静候通知,一旦接到通知,就会得到一个整数)。

4. 主题有了新的数据(现在鸭子和其他所有观察者都会受到通知:主题已经改变)

5. 老鼠对象要求从观察者中把自己除名(老鼠已经观察次主题太久,决定不再当观察者了)。

6. 老鼠离开了(主题知道老鼠的请求后,把它从观察者中移除了)。

7. 主题有了一个新的整数(除了老鼠之外,每个观察者都会收到通知,如果老鼠又想当观察者了,它还可以再回来)

定义观察者模式

当你试图勾勒观察者模式时,可以利用报纸订阅服务,以及出版这和订阅者比你这一切。在程序设计中,观察者模式通常被定义为:

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态是,它的所有依赖者都会收到通知并自动更新。

我们和之前的例子做个对比:

主题和观察者定义了一对多的关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。

现在你可能有疑问,这和一对多的关系有何关联?

利用观察者模式,主题是具有状态的对象,并且可以控制这些状态。也就是说,有一个具有状态的主题。另一方面,观察者使用这些状态,虽然这些状态不属于他们。有许多观察者,依赖主题告诉他们状态何时改变了。这就产生了一个关系:一个主题对多个观察者的关系

观察者和主题之间的依赖关系是如何产生的?

主题是真正拥有数据的人,观察者是主题的依赖者,在数据变化时更新,这样比起让许多对象控制同一份数据来,可以得到更干净的 OO 设计。

观察者模式的应用案例

观察者模式在实际应用中有许多的案例,比如信息的聚合。无论格式为 RSS、Atom 还是其它,思想多事一样的:你追随某个信息源,当它每次更新时,你都会收到关于更新的通知。
事件驱动系统是一个可以使用观察者模式的例子。在这种系统中,监听者被用于监听特定的事件。监听者的事件被创建出来时就会触发它们。这个事件可以使键入某个特定的键、移动鼠标或者其他。事件扮演发布者的角色,监听者则扮演观察者的角色。

Python 实现

现在,让我们回到文章开始的那个问题。

这里我们可以实现一个基类 Publisher,包括添加、删除及通知观察者这些公用功能。DefaultFormatter 类继承自 Publisher,并添加格式化程序特定的功能。

文章开头问题的类图

Publisher 的代码如下:

import itertools'''
观察者模式实现
'''class Publisher:def __init__(self):self.observers = set()def add(self, observer, *observers):for observer in itertools.chain((observer, ), observers):self.observers.add(observer)observer.update(self)def remove(self, observer):try:self.observers.discard(observer)except ValueError:print('Failed to remove: {}'.format(observer))def notify(self):[observer.update(self) for observer in self.observers]

现在,打算使用观察者模式的模型或类都应该继承 Publisher 类。该类用 set 来保存观察者对象。当用户向 Publisher 注册新的观察者对象时,观察者的 update() 方法会执行,这使得它能够用模型当前的状态初始化自己。模型状态发生变化时,应该调用继承而来的 notify() 方法,这样的话,就会执行每个观察者对象的 update() 方法,以确保他们都能反映出模型的最新状态。

add() 方法的写法值得注意,这里是为了支持可以接受一个或多个观察者对象。这里我们采用了itertools.chain() 方法,它可以接受任意数量的 iterable,并返回单个iterable。遍历这个 iterable,也就相当于依次遍历参数里的那些 iterable。

接下来是 DefaultFomatter 类。__init__() 做的第一件事就是调用基类的__init__() 方法,因为这在 Python 中没法自动完成。DefaultFormatter 实例有自己的名字,这样便于我们跟踪其状态。对于_data 变量,我们使用了名称改编来声明不能直接访问该变量。DefaultFormatter_data 变量用作一个整数,默认值为0。

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()

  • __str__() 方法返回关于发布者名称和 _data 值的信息。type(self).__name 是一种获取类名的方便技巧,避免硬编码类名。(不过这会降低代码的可读性)
  • data() 方法有两个,第一个使用了 @property 装饰器来提供_data 变量的读访问方式。这样,我们就能使用 object.data 来代替 object._data。第二个 data() 方法使用了@setter 装饰器,改装饰器会在每次使用赋值操作符(=)为_data 变量赋值时被调用。该方法也会尝试把新值强制转换为一个整数,并在转换失败时处理异常。

接下来是添加观察者。HexFormatterBinaryFormatter 功能基本相似。唯一的不同在于如何格式化从发布者那获取到的数据值,即十六进制和二进制格式化。

class HexFormatter:def update(self, publisher):print("{}: '{}' has now hex data= {}".format(type(self).__name__,publisher.name, hex(publisher.data)))class BinaryFormatter:def update(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 = 4.2print(df)if __name__ == '__main__':main()

完整代码参考:https://gist.github.com/gusibi/93a000c79f3d943dd58dcd39c4b547f1

运行代码:

python observer.py
## output
DefaultFormatter: 'test1' has data = 0HexFormatter: 'test1' has now hex data= 0x0
HexFormatter: 'test1' has now hex data= 0x3
DefaultFormatter: 'test1' has data = 3BinaryFormatter: 'test1' has now bin data= 0b11
BinaryFormatter: 'test1' has now bin data= 0b10101
HexFormatter: 'test1' has now hex data= 0x15
DefaultFormatter: 'test1' has data = 21BinaryFormatter: 'test1' has now bin data= 0b101000
DefaultFormatter: 'test1' has data = 40BinaryFormatter: 'test1' has now bin data= 0b101000
Error: invalid literal for int() with base 10: 'hello'
DefaultFormatter: 'test1' has data = 40BinaryFormatter: 'test1' has now bin data= 0b100
DefaultFormatter: 'test1' has data = 4

在输出中我们看到,添加额外的观察者,就会出现更多的输出;一个观察者被删除后就不再被通知到。

总结

这一篇我们介绍了观察者模式的原理以及 Python 代码的实现。在实际的项目开发中,观察者模式广泛的运用于 GUI 编程,而且在仿真及服务器等其他时间处理架构中也能用到,比如:数据库触发器Django 的信号系统Qt GUI 应用程序框架的信号(signal)与槽(slot)机智以及WebSocket的许多用例。

参考链接

  • The 10 Minute Guide to the Observer Pattern in Python
  • Observer

最后,感谢女朋友支持。

欢迎关注(April_Louisa)请我喝芬达
欢迎关注请我喝芬达



推荐阅读
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Oracle10g备份导入的方法及注意事项
    本文介绍了使用Oracle10g进行备份导入的方法及相关注意事项,同时还介绍了2019年独角兽企业重金招聘Python工程师的标准。内容包括导出exp命令、删用户、创建数据库、授权等操作,以及导入imp命令的使用。详细介绍了导入时的参数设置,如full、ignore、buffer、commit、feedback等。转载来源于https://my.oschina.net/u/1767754/blog/377593。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 深度学习中的Vision Transformer (ViT)详解
    本文详细介绍了深度学习中的Vision Transformer (ViT)方法。首先介绍了相关工作和ViT的基本原理,包括图像块嵌入、可学习的嵌入、位置嵌入和Transformer编码器等。接着讨论了ViT的张量维度变化、归纳偏置与混合架构、微调及更高分辨率等方面。最后给出了实验结果和相关代码的链接。本文的研究表明,对于CV任务,直接应用纯Transformer架构于图像块序列是可行的,无需依赖于卷积网络。 ... [详细]
author-avatar
手机用户2502869883
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有