python设计模式-观察者模式

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

咱们看下需求:html

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

一个错误的实现多是这样的:python

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

咱们能够这么使用:git

number = NumberFormatter(10)
number.show_data()

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

要解决这个问题,就能够用到咱们此次要介绍的观察者模式了。shell

什么是观察者模式

认识观察者模式

咱们先看看报纸和杂志的订阅是怎么回事:数据库

  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 = name
        self._data = 0

    def __str__(self):
        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):
        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 = 3
    print(df)

    print()
    bf = BinaryFormatter()
    df.add(bf)
    df.data = 21
    print(df)

    print()
    df.remove(hf)
    df.data = 40
    print(df)

    print()
    df.remove(hf)
    df.add(bf)

    df.data = 'hello'
    print(df)

    print()
    df.data = 4.2
    print(df)


if __name__ == '__main__':
    main()

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

运行代码:

python observer.py    
## output
DefaultFormatter: 'test1' has data = 0

HexFormatter: 'test1' has now hex data= 0x0
HexFormatter: 'test1' has now hex data= 0x3
DefaultFormatter: 'test1' has data = 3

BinaryFormatter: 'test1' has now bin data= 0b11
BinaryFormatter: 'test1' has now bin data= 0b10101
HexFormatter: 'test1' has now hex data= 0x15
DefaultFormatter: 'test1' has data = 21

BinaryFormatter: 'test1' has now bin data= 0b101000
DefaultFormatter: 'test1' has data = 40

BinaryFormatter: 'test1' has now bin data= 0b101000
Error: invalid literal for int() with base 10: 'hello'
DefaultFormatter: 'test1' has data = 40

BinaryFormatter: 'test1' has now bin data= 0b100
DefaultFormatter: 'test1' has data = 4

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

总结

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

参考连接


最后,感谢女友支持。

欢迎关注(April_Louisa) 请我喝芬达
欢迎关注 请我喝芬达
相关文章
相关标签/搜索