“relax_please”项目中观察者模式解析
1 项目简介
作者用python实现的一个项目,提醒程序员不要过度工作,使用观察者模式实现,调皮的作者把项目命名为:休息吧,程序员!
代码链接如下:https://github.com/cooljacket/relax_please/blob/master/relax_please.py
2 观察者模式简介
建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
3 观察者模式在该项目中的原理分析
这个项目由以下两部分组成:
1) 监听器:监听系统的键盘输入、系统时间。
2) 发送提醒:一旦监听的事件发生(比如超过11点,持续工作超过1小时等),那么监听器就会触发“发送提醒器”,提醒器会以某种方式发送通知给使用者。
这个项目的对象关系是一个很经典的“观察者模式”的模型,UML图如下:
1). 监听器抽象接口Listener,它实现了所有监听器共有的功能,即绑定观察者,解绑观察者,(在所监听的事件发生的时候)通知所有绑定的观察者,以及监听事件。
class Listener: def __init__(self): self._observers = [] def attach(self, observer): if not observer in self._observers: self._observers.append(observer) def detach(self, observer): try: self._observers.remove(observer) except ValueError, e: pass def notify(self): for observer in self._observers: observer.update() def listening(self): pass
2) 需要监听两个事件,一个是使用电脑的情况(通过键盘输入来估计),一个是当前的工作时间(看看是否有熬夜),所以继承实现了两个具体的监听器,KeyBoardListener和SayGoodNightListener,它们都只需要实现listening函数即可,因为其它的都在抽象类Listener中实现好了!
class KeyBoardListener(Listener): """使用观察者模式来实现对键盘敲击的监听!""" format = "llHHI" size = struct.calcsize(format) keyboardDevice = "/dev/input/event3" fd = os.open(keyboardDevice, os.O_RDWR) def __init__(self, maxWorkTime=3600, leastRelaxTime=600, notifyInterval=60): Listener.__init__(self) self.maxWorkTime = maxWorkTime self.leastRelaxTime = leastRelaxTime self.notifyInterval = notifyInterval self.lastInputTime = time.time() self.lastNotifyTime = time.time() - notifyInterval self.start_work_time = time.time() def listening(self): while True: op = os.read(self.fd, self.size) # timestamp, subsecond, type, code, value = struct.unpack(format, op) now, _, _, _, _ = struct.unpack(self.format, op) # 如果中间有休息过了,那么更新开始工作的时间 if now - self.lastInputTime >= self.leastRelaxTime: self.start_work_time = now elif now - self.start_work_time >= self.maxWorkTime: # 如果持续工作超过指定时间段,并且距离上次提醒的时间超过设定区间,就开始发提醒! if now - self.lastNotifyTime >= self.notifyInterval: self.notify() self.lastNotifyTime = now self.lastInputTime = now
class SayGoodNightListener(Listener): def __init__(self, night=23, morning=5, notifyInterval=30): Listener.__init__(self) self.morning = morning self.night = night self.notifyInterval = notifyInterval def listening(self): while True: now = time.localtime(time.time()) if now.tm_hour >= self.night or now.tm_hour <= self.morning: self.notify() time.sleep(self.notifyInterval)
3)观察者抽象类Observer,这个其实只是个接口而已,不是一个类,因为update()虽然是共有的接口函数,但它的实体是因人而异的,没法在这个类里给出一个default的实现(对比一下Listener的notify方法就知道了)。
class Observer: def update(self): raise NotImplementedError("Must subclass me")
4)该项目,目前的发通知给使用者,只实现了一种方式:发送泡泡弹窗到桌面右上角,所以只写了一个NotifySendObserver类。
class NotifySendObserver(Observer): """通过ubuntu上的nofity-send在屏幕右上角发送提醒信息""" def __init__(self, title, msg): self.title = title self.msg = msg def update(self): self.sendNotification(self.title, self.msg) def sendNotification(self, title, msg): os.system('export DISPLAY=:0.0; notify-send "{0}" "{1}"'.format(title, msg))
值得注意的是:一个监听器可以有多个观察者,而一般一个观察者只能有一个监听器。比如监听是否熬夜这个事件,可以有多种通知方式,比如响闹铃,发送泡泡弹窗到桌面的右上角,记录熬夜情况到日志文件以备后续分析,等等。而一般一个观察者都是特化了的,不会用在多个监听器上面,比如你熬夜的时候响一段摇篮曲,是针对特定的这个(熬夜)事件的!
4 观察者模式给该项目带来了哪些好处
对于这个项目而言:观察者模式把监测事件和响应事件这两个职责给分离了,各司其职。监听器监测使用者有没有熬夜,有的话,就通过接口告诉观察者,观察者再发通知给使用者,叫ta赶紧睡觉。
即观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。观察者模式在观察目标和观察者之间建立一个抽象的耦合。且观察者模式符合“开闭原则”的要求。