引言
因为最近一直有在接触关于设计模式的一些案例,还有消息队列,使用了很多,而消息队列的背后其实就是一种设计模式,并且因为有写一个观察者模式的demo来初始化各种模型类,所以本篇想详细说说观察者模式的原理以及各种对比。
观察者模式介绍
观察者模式在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。它能解决:
- 在不使对象紧密耦合的情况下,应定义对象之间的一对多依赖关系。
- 应确保当一个对象更改状态时,将自动更新不限数量的从属对象。
- 一个对象应该可以通知其他对象的不限数目的对象。
通过定义一个直接更新从属对象状态的对象(对象)来定义对象之间的一对多依赖关系是不灵活的,因为它将主题与特定的从属对象耦合在一起。从性能的角度来看,或者如果对象实现紧密耦合(从每秒执行数千次的低级内核结构来看),这仍然是有意义的。紧密耦合的对象在某些情况下可能难以实现,并且难以重用,因为它们引用并知道(以及如何更新)具有不同接口的许多不同对象。在其他情况下,紧密耦合的对象可能是更好的选择,因为编译器将能够在编译时检测错误并在CPU指令级别优化代码。
上述定义来自维基百科,如果仔细多读几遍,发现还是很好理解的。我举个例子,就像我在写这篇文章的时候,我需要想好整个题纲怎么去构思,分出几个小点,但当然有时候也会没有大纲想到哪就写啥。。。嗯,如果换成思维导图,那么就更好理解了,如果一个知识点没有一条完整的主线,等到下面的支线发生问题的时候,改动一个可能会导致连锁,然后越改越乱,会让整个结构很是臃肿。所以我们需要做的是那种可插拔式的,放入后,上一级分裂成二叉树自动进行关联,或者其它搜索树;取出,又恢复成单节点,就不需要我们手动配置。
而观察者模式中的通知也可以举个例子,我下面参考文献中有个例子很不错。假设某大公司找你去初试,面完后你觉得很不错,公司氛围融洽,薪资福利到位,对面hr也觉得OK,但最近你期待的岗位已经没有空缺了,但马上会空出来,所以等那个时候hr就会通知你以及其它候选人,然后再来一次复试,复试过了就如愿以偿。
所以这里的公司hr就是Subject,维护着Observers(和你一样的候选人),为某些event(比如职位空缺)来 通知(notify) 观察者。
这种通知-注册交互也称为 publish-subscribe。
观察者与发布订阅
在观察者模式中的Subject就像一个发布者(Publisher),观察者(Observer)完全和订阅者(Subscriber)关联。subject通知观察者就像一个发布者通知他的订阅者。这也就是很多书和文章使用“发布-订阅”概念来解释观察者设计模式。 但是这里还有另外一个流行的模式叫做发布-订阅设计模式 。它的概念和观察者模式非常类似。最大如下图所是:
没错,就是中间再解耦的event channel。可以看出,发布订阅模式相比观察者模式多了个事件通道,事件通道作为调度中心,管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系。即订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。
所以,可以总结以上两者的差异为:
-
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
-
在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
-
观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
-
观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。
关于订阅发布的例子,之前有写过一篇python-kafka的搭建使用笔记,除了kafka,还有rabbitmq等一系列消息队列都是应用级的说明,下面就介绍观察者在python中的具体实现。
观察者实现
在python中,由于没有像Java一样支持观察者模式的标准库组件,所以python中观察者需要自己实现,最简单的方式是创建两个类为Observer和Observable类,具体实例如下。
观察者示例
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 = 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 = 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 binary 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 = 13
print(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 binary data = 0b10101
DefaultFormatter: 'test1' has data = 21
BinaryFormatter: 'test1' has now binary data = 0b1101
DefaultFormatter: 'test1' has data = 13
"""
这是《python设计模式》书中第十三章观察者模式的比较经典的一个demo,这是打算使用观察者模式的模型或类都应该继承Publisher类。该类用set来保存观察者对象。当用户(HexFormatter和BinaryFormatter)向Publisher注册新的观察者对象时,观察者的update()方法会执行,这使得它能够用模型当前的状态初始化自己。模型状态发生变化时,应该调用继承而来的notify()方法,这样的话,就会执行每个观察者对象的update()方法,以确保他们都能反映出模型的最新状态。而DefaultFormatter类继承自Publisher,此类包含要被 广播 的数据,并在数据更新时调用通知观察者的方法。(这里的广播机制也类似于numpy数组,当然我这里只是提一嘴,可以忽略。)
看起来上面的例子还是有点冗余,可以参考文献2中的改进写法:
from abc import ABCMeta, abstractmethod
class NewsPublisher: # subject
def __init__(self):
self.__subscribers = []
self.__latestNews = None
def attach(self, subscriber):
self.__subscribers.append(subscriber)
def detach(self):
return self.__subscribers.pop()
def notifySubscribers(self):
for sub in self.__subscribers:
sub.update()
def addNews(self, news):
self.__latestNews = news
def getNews(self):
return 'Got News:' + self.__latestNews
class Subscriber(metaclass=ABCMeta): # Observer
@abstractmethod
def update(self):
pass
class ConcreteSubscriber1: # ConcreteObserver
def __init__(self, publisher):
self.publisher = publisher
self.publisher.attach(self)
def update(self):
print(type(self).__name__, self.publisher.getNews())
class ConcreteSubscriber2:
def __init__(self, publisher):
self.publisher = publisher
self.publisher.attach(self)
def update(self):
print(type(self).__name__, self.publisher.getNews())
news_publisher = NewsPublisher()
for Subscribers in [ConcreteSubscriber1, ConcreteSubscriber2]: # 创建观察者对象
Subscribers(news_publisher)
news_publisher.addNews('HELLO WORLD')
news_publisher.notifySubscribers()
news_publisher.detach()
news_publisher.addNews('SECOND NEWS')
news_publisher.notifySubscribers()
'''
ConcreteSubscriber1 Got News:HELLO WORLD
ConcreteSubscriber2 Got News:HELLO WORLD
ConcreteSubscriber1 Got News:SECOND NEWS
'''
上述的ABCMeta,是约定俗称的实现这个方法,加上@abc.abstractmethod装饰器后严格控制子类必须实现这个方法,这也算是python里的一个多态性的地方,很久之前有写过一篇关于抽象类的博客,但当时还不太明白具体含义,只是在Django中看到有大规模使用,博客将在参考文献中引出。
观察者详解与进阶
照上面的代码看起来,算是完成了一个简单的demo,但似乎如果要弄成通用,或者说达到Java的级别,上述代码又过于简单,因为最近我也在学Java,关于Java为什么将观察者放于util中有所疑惑,但看到一篇外文有点懂了,比较大的原因可能是Java具有为线程同步提供内置支持的synced关键字。下面就从python角度写出这个类。
首先,我们可以尝试自己写一个提供thread synchronization 的ToSynch 类:
import threading
class ToSynch:
def __init__(self):
self.mutex = threading.RLock()
self.val = 1
def aSynchronizedMethod(self):
self.mutex.acquire()
try:
self.val += 1
return self.val
finally:
self.mutex.release()
但以这样的思路写下去是无聊并且难以再写,所以这个时候 Bruce Eckel 作者找到了另一个有相关研究的Peter Norvig ,直接用它提供的类和函数更加有效:
'''Simple emulation of Java's 'synchronized'
keyword, from Peter Norvig.'''
import threading
def synchronized(method):
def f(*args):
self = args[0]
self.mutex.acquire()
# print(method.__name__, 'acquired')
try:
# return apply(method, args)
return method(*args)
finally:
self.mutex.release()
# print(method.__name__, 'released')
return f
def synchronize(klass, names=None):
"""Synchronize methods in the given class.
Only synchronize the methods whose names are
given, or all methods if names=None."""
if type(names)==type(''): names = names.split()
for (name, val) in klass.__dict__.items():
if callable(val) and name != '__init__' and \
(names == None or name in names):
# print("synchronizing", name)
setattr(klass, name, synchronized(val))
# You can create your own self.mutex, or inherit
# from this class:
class Synchronization:
def __init__(self):
self.mutex = threading.RLock()
这里我在原基础上修改了两个地方,一个是加了setattr,因为原文里好像没有任何修饰,但到我目前python3.7的版本会有报错,还有一个是return apply(method, args) ,我没有找到apply的方法在哪,后来查了下资料,更改成了return method。那么接着往下:
这个synchronized( ) 函数采用的方法和把它封装在其将所述互斥的功能,在某种程度上这是装饰器模式,但创建和使用起来要简单得多,你只需要用:
myMethod = synchronized(myMethod)
用互斥锁包围你的方法。
synchronize( ) 是一个遍历函数,而要让它用于工作,必须在每个使用synchronize( ) 的类中创建一个self.mutex,可以由类作者手动创建,但是使用继承更为一致,因此提供了基类 Synchronization。
这是对同步模块的简单测试:
# Util/TestSynchronization.py
from Synchronization import *
# To use for a method:
class C(Synchronization):
def __init__(self):
Synchronization.__init__(self)
self.data = 1
def m(self):
self.data += 1
return self.data
m = synchronized(m)
def f(self): return 47
def g(self): return 'spam'
# So m is synchronized, f and g are not.
c = C()
# On the class level:
class D(C):
def __init__(self):
C.__init__(self)
# You must override an un-synchronized method
# in order to synchronize it (just like Java):
def f(self): C.f(self)
# Synchronize every (defined) method in the class:
synchronize(D)
d = D()
d.f() # Synchronized
d.g() # Not synchronized
d.m() # Synchronized (in the base class)
class E(C):
def __init__(self):
C.__init__(self)
def m(self): C.m(self)
def g(self): C.g(self)
def f(self): C.f(self)
# Only synchronizes m and g. Note that m ends up
# being doubly-wrapped in synchronization, which
# doesn't hurt anything but is inefficient:
synchronize(E, 'm g')
e = E()
e.f()
e.g()
e.m()
您必须为Synchronization调用基类构造函数,仅此而已。在C类中,您可以看到对m使用了synchronize( ),而使f和g保持不变。D类的所有方法都进行了同步,E类使用了便利函数来同步m和 g。请注意,由于m最终被同步了两次,因此每次调用都会被输入两次,并留下两次,这不是很理想[可能有解决方案]:
# Util/Observer.py
# Class support for "observer" pattern.
from Synchronization import *
class Observer:
def update(observable, arg):
'''Called when the observed object is
modified. You call an Observable object's
notifyObservers method to notify all the
object's observers of the change.'''
pass
class Observable(Synchronization):
def __init__(self):
self.obs = []
self.changed = 0
Synchronization.__init__(self)
def addObserver(self, observer):
if observer not in self.obs:
self.obs.append(observer)
def deleteObserver(self, observer):
self.obs.remove(observer)
def notifyObservers(self, arg = None):
'''If 'changed' indicates that this object
has changed, notify all its observers, then
call clearChanged(). Each observer has its
update() called with two arguments: this
observable object and the generic 'arg'.'''
self.mutex.acquire()
try:
if not self.changed: return
# Make a local copy in case of synchronous
# additions of observers:
localArray = self.obs[:]
self.clearChanged()
finally:
self.mutex.release()
# Updating is not required to be synchronized:
for observer in localArray:
observer.update(self, arg)
def deleteObservers(self): self.obs = []
def setChanged(self): self.changed = 1
def clearChanged(self): self.changed = 0
def hasChanged(self): return self.changed
def countObservers(self): return len(self.obs)
synchronize(Observable,
"addObserver deleteObserver deleteObservers " +
"setChanged clearChanged hasChanged " +
"countObservers")
使用此库,这里是观察者模式的示例:
# Observer/ObservedFlower.py
# Demonstration of "observer" pattern.
import sys
sys.path += ['../util']
from Observer import Observer, Observable
class Flower:
def __init__(self):
self.isOpen = 0
self.openNotifier = Flower.OpenNotifier(self)
self.closeNotifier= Flower.CloseNotifier(self)
def open(self): # Opens its petals
self.isOpen = 1
self.openNotifier.notifyObservers()
self.closeNotifier.open()
def close(self): # Closes its petals
self.isOpen = 0
self.closeNotifier.notifyObservers()
self.openNotifier.close()
def closing(self): return self.closeNotifier
class OpenNotifier(Observable):
def __init__(self, outer):
Observable.__init__(self)
self.outer = outer
self.alreadyOpen = 0
def notifyObservers(self):
if self.outer.isOpen and \
not self.alreadyOpen:
self.setChanged()
Observable.notifyObservers(self)
self.alreadyOpen = 1
def close(self):
self.alreadyOpen = 0
class CloseNotifier(Observable):
def __init__(self, outer):
Observable.__init__(self)
self.outer = outer
self.alreadyClosed = 0
def notifyObservers(self):
if not self.outer.isOpen and \
not self.alreadyClosed:
self.setChanged()
Observable.notifyObservers(self)
self.alreadyClosed = 1
def open(self):
self.alreadyClosed = 0
class Bee:
def __init__(self, name):
self.name = name
self.openObserver = Bee.OpenObserver(self)
self.closeObserver = Bee.CloseObserver(self)
# An inner class for observing openings:
class OpenObserver(Observer):
def __init__(self, outer):
self.outer = outer
def update(self, observable, arg):
print("Bee " + self.outer.name + \
"'s breakfast time!" )
# Another inner class for closings:
class CloseObserver(Observer):
def __init__(self, outer):
self.outer = outer
def update(self, observable, arg):
print("Bee " + self.outer.name + \
"'s bed time!")
class Hummingbird:
def __init__(self, name):
self.name = name
self.openObserver = \
Hummingbird.OpenObserver(self)
self.closeObserver = \
Hummingbird.CloseObserver(self)
class OpenObserver(Observer):
def __init__(self, outer):
self.outer = outer
def update(self, observable, arg):
print("Hummingbird " + self.outer.name + \
"'s breakfast time!")
class CloseObserver(Observer):
def __init__(self, outer):
self.outer = outer
def update(self, observable, arg):
print("Hummingbird " + self.outer.name + \
"'s bed time!")
f = Flower()
ba = Bee("Eric")
bb = Bee("Eric 0.5")
ha = Hummingbird("A")
hb = Hummingbird("B")
f.openNotifier.addObserver(ha.openObserver)
f.openNotifier.addObserver(hb.openObserver)
f.openNotifier.addObserver(ba.openObserver)
f.openNotifier.addObserver(bb.openObserver)
f.closeNotifier.addObserver(ha.closeObserver)
f.closeNotifier.addObserver(hb.closeObserver)
f.closeNotifier.addObserver(ba.closeObserver)
f.closeNotifier.addObserver(bb.closeObserver)
# Hummingbird 2 decides to sleep in:
f.openNotifier.deleteObserver(hb.openObserver)
# A change that interests observers:
f.open()
f.open() # It's already open, no change.
# Bee 1 doesn't want to go to bed:
f.closeNotifier.deleteObserver(ba.closeObserver)
f.close()
f.close() # It's already closed; no change
f.openNotifier.deleteObservers()
f.open()
f.close()
感兴趣的事件是花朵可以打开或关闭。由于使用了内部类惯用语,因此这两个事件都是可以分别观察的现象。OpenNotifier和CloseNotifier都继承了Observable,因此它们可以访问setChanged(),并且可以传递给任何需要Observable的对象。
总结
本篇总结了关于观察者模式的原理,通过自造Synchronization实现了和Java类似的线程同步机制,但可能还是有些简单,因为观察者模式也算是各种应用级软件或者库都有使用,等之后更加精进了,会回头来修改相关知识点。
参考与推荐:
[1]. https://en.wikipedia.org/wiki/Observer_pattern
[2]. 观察者模式 vs 发布-订阅模式
[3]. python通用设计模式 第一版
[4]. python设计模式之观察者模式
[5]. Python 3 Patterns, Recipes and Idioms
[6]. Java设计模式之观察者模式的两种实现