[Python设计模式] 第14章 老板来了——观察者模式
github地址:https://github.com/cheesezh/python_design_patterns
题目
用程序模拟以下情景,在一个办公室里,当老板进门的时候,前台秘书就偷偷通知办公室里的同事:“老板来了”,办公室里的同事就会停止观看股票,继续工作。
基础版本
class Secretary():
"""
通知者
"""
def __init__(self):
self.observers = []
self.action = None
def attach(self, observer):
self.observers.append(observer)
def notify(self):
for observer in self.observers:
observer.update()
class Observers():
"""
观察者
"""
def __init__(self, name, informer):
self.name = name
self.informer = informer
def update(self):
print("{} {}关闭股票行情, 继续工作.".format(self.informer.action, self.name))
def main():
informer = Secretary()
hh = Observers("贺贺", informer)
mm = Observers("曼曼", informer)
informer.attach(hh)
informer.attach(mm)
informer.action = "老板来了!"
informer.notify()
main()
老板来了! 贺贺关闭股票行情, 继续工作.
老板来了! 曼曼关闭股票行情, 继续工作.
点评
- 上述代码基本能够表示设定的场景,但是有一个问题,“通知者”和“观察者”这两个类互相耦合,即双向耦合;
- 假设另一类“观察者”不看股票,而是在看NBA比赛,那么这类“观察者”对应的update操作就不应该是“关闭股票行情”,而是“关闭NBA直播”;
- 此外,除了前台秘书可以作为“通知者”,老板其实也是“通知者”,当老板来了,各个“观察者”也应该及时回到工作状态;
- 综上,需要对“观察者”和“通知者”进行进一步抽象
改进版本——双向解耦
from abc import ABCMeta,abstractmethod
class Informer():
"""
抽象通知者
"""
__metaclass__ = ABCMeta
@abstractmethod
def attach(self, observer):
pass
@abstractmethod
def detach(self, observer):
pass
@abstractmethod
def notify(self):
pass
class Boss(Informer):
"""
具体通知者
"""
def __init__(self):
self.observers = []
self.action = None
# 老板特有的初始化操作
def attach(self, observer):
print("聘用:{}".format(observer.name))
self.observers.append(observer)
def detach(self, observer):
print("解聘:{}".format(observer.name))
self.observers.remove(observer)
def notify(self):
print("--老板发出强大的气场--")
for o in self.observers:
o.update()
class Secretary(Informer):
"""
具体通知者
"""
def __init__(self):
self.observers = []
self.action = None
# 秘书特有的初始化操作
def attach(self, observer):
print("关系和谐:{}".format(observer.name))
self.observers.append(observer)
def detach(self, observer):
print("产生矛盾:{}".format(observer.name))
self.observers.remove(observer)
def notify(self):
print("--秘书发送即时消息--")
for o in self.observers:
o.update()
class Observer():
"""
抽象观察者
"""
__mataclass__ = ABCMeta
@abstractmethod
def __init__(self, name, informer): # 之所以要有informer,是为了在观察者内部访问到informer的对象
pass
@abstractmethod
def update(self):
pass
class StockObserver(Observer):
"""
具体观察者
"""
def __init__(self, name, informer):
self.name = name
self.informer = informer
def update(self):
print("{} {}关闭股票行情, 继续工作.".format(self.informer.action, self.name))
class NBAObserver(Observer):
"""
具体观察者
"""
def __init__(self, name, informer):
self.name = name
self.informer = informer
def update(self):
print("{} {}关闭NBA直播, 继续工作.".format(self.informer.action, self.name))
def main():
boss = Boss()
hh = StockObserver("贺贺", boss)
mm = NBAObserver("曼曼", boss)
boss.attach(hh)
boss.attach(mm)
boss.action = "我回来了!"
boss.notify()
secretary = Secretary()
mr = StockObserver("鸣人", secretary)
zz = NBAObserver("佐助", secretary)
xy = NBAObserver("小樱", secretary)
secretary.attach(mr)
secretary.attach(zz)
secretary.attach(xy)
secretary.detach(xy)
secretary.action = "老板回来了!"
secretary.notify()
main()
聘用:贺贺
聘用:曼曼
--老板发出强大的气场--
我回来了! 贺贺关闭股票行情, 继续工作.
我回来了! 曼曼关闭NBA直播, 继续工作.
关系和谐:鸣人
关系和谐:佐助
关系和谐:小樱
产生矛盾:小樱
--秘书发送即时消息--
老板回来了! 鸣人关闭股票行情, 继续工作.
老板回来了! 佐助关闭NBA直播, 继续工作.
观察者模式(发布-订阅模式)
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
Observer类,即抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个update方法。
ConcreteSubject类,叫做具体主题或者具体通知者,将有关状态存入具体观察者对象。在具体主题的内部状态改变时,给所有登记过的观察者发出通知。
ConcreteObserver类,即具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如有需要,具体观察者角色可以保存一个指向具体主题对象的引用,以便获取具体主题对象的状态。
观察者模式特点
讲一个系统分割成一系列相互写作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护,扩展和重用都带来不便[DP]。而观察者的关键对象使主题Subject和观察者Observer,一个主题可以有任意树木的依赖它的Observer,一旦Subject状态发生变化,所有的Observer都可以得到通知。
那么什么时候使用观察者模式呢?
当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象有待改变时,需要考虑使用观察者模式。另外,当一个抽象模型有两个方面,其中一个方面依赖另一个方面,这时候使用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
总地来将,观察者模式所做的工作其实就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体实现。从而使得各自的变化不会影响另一边的变化。
找我内推: 字节跳动各种岗位
作者:
ZH奶酪(张贺)
邮箱:
cheesezh@qq.com
出处:
http://www.cnblogs.com/CheeseZH/
*
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。