什么是监听模式

在对象间定义一种一对多的依赖关系,当这个对象状态发生变化时,所有的依赖它的对象都会被通知并自动更新。
监听模式是一种一对多的关系,可以有任意个观察者对象同时监听某个对象。监听的对象叫做观察者,被监听的对象叫做被观察者(Observable)。被观察者对象在状态或内容发生变化时,会通知所有观察者对象,使它们能做相应的变化。

监听模式设计思想

监听者模式又名观察者模式,比如你在烧开水的时候看着它开没开,你就是观察者,水就是被观察者,观察者模式是对象的行为模式,又叫发布/订阅,模型/视图,源/监听模式,监听模式的核心思想是被观察者与观察者之间建立一种自动触发关系。

代码架构

  1. class Observer:
  2. """观察者基类"""
  3. def update(self, observable, object):
  4. pass
  5. class Observable:
  6. """被观察者的基类"""
  7. def __init__(self):
  8. self.__observers = []
  9. def add_observer(self, observer):
  10. self.__observers.append(observer)
  11. def remove_observer(self, observer):
  12. self.__observers.remove(observer)
  13. def notify_observers(self, object=0):
  14. for observer in self.__observers:
  15. observer.update(self, object)
  16. class WaterHeater(Observable):
  17. """热水器---被观察者"""
  18. def __init__(self):
  19. super().__init__()
  20. self.__temp = 25
  21. def set_temp(self, temp):
  22. self.__temp = temp
  23. print('当前温度%s' % self.__temp)
  24. self.notify_observers()
  25. def get_temp(self):
  26. return self.__temp
  27. class WashingMode(Observer):
  28. """洗澡模式"""
  29. def update(self, observable, object):
  30. if all([observable.get_temp() >= 50, observable.get_temp() < 70]):
  31. print('洗澡行为')
  32. class DrinkingMode(Observer):
  33. """喝水模式"""
  34. def update(self, observable, object):
  35. if observable.get_temp() > 100:
  36. print('洗澡行为')
  37. if __name__ == '__main__':
  38. water_heater = WaterHeater()
  39. water_heater.add_observer(WashingMode())
  40. water_heater.add_observer(DrinkingMode())
  41. for i in range(10, 150):
  42. water_heater.get_temp()
  43. water_heater.set_temp(i)

模型说明

设计要点

要明确谁是观察者谁是被观察者,一般观察者与被观察者是多对一的关系。
Observable在发送通知的时候,无须指定具体的observer,观察者自己决定是否订阅消息
被观察者至少有三个方法:添加观察者,删除观察者,通知观察者。观察者者少要有一个方法:更新方法,即更新当前的内容,做出相应的处理。

推送模型和拉模型

推模型

被观察者对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或者部分数据,一般在这种模型的实现中,会把被观察者对象中的全部或者部分信息通过update参数传递给观察者。

拉模型

被观察者在通知观察者的时候,只传递少量的信息,如果观察者需要更具体的信息,由观察者主动到被观察对象中获取,相当于观察者从被观察者对象中拉取数据。一般在模型实现中会把被观察者对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

实战应用

在互联网广泛普及和快速发展的时代,信息安全被越来越多的人重视,其中账户安全是信息安全最重要的部分,很多网站都有一个账号登录检测和诊断机制,当账户异常登录时,会以短信或者邮件的方式将登录信息发送给已经绑定的手机或者邮箱。
登录异常就是状态的改变,服务器会记录你最近登录的时间、地址等,从而得知你常用的登录的地区,如果那次检测你登录的地区与经常登录的地区差别很大,则认为是异常登录,我邮件和短信则认为是监听者,只要异常出现,就自动发送短信或者邮件。

代码:

  1. class Observer:
  2. """观察者基类"""
  3. def update(self, observable, object):
  4. pass
  5. class Observable:
  6. """被观察者的基类"""
  7. def __init__(self):
  8. self.__observers = []
  9. def add_observer(self, observer):
  10. self.__observers.append(observer)
  11. def remove_observer(self, observer):
  12. self.__observers.remove(observer)
  13. def notify_observers(self, object=None):
  14. for observer in self.__observers:
  15. observer.update(self, object)
  16. class Account(Observable):
  17. def __init__(self):
  18. super().__init__()
  19. self.__latest_ip = {}
  20. self.__latest_region = {}
  21. def __get_region(self, ip):
  22. ip_regions = {
  23. '192.168.2.235': '深圳',
  24. '192.166.3.37': '蕲春'
  25. }
  26. return ip_regions.get(ip, '')
  27. def login(self, name, ip, time):
  28. region = self.__get_region(ip)
  29. if self.__is_long_distance(name, region):
  30. self.notify_observers({'name': name, 'ip': ip, 'region':region, 'time': time})
  31. self.__latest_region[name] = region
  32. self.__latest_ip[name] = ip
  33. def __is_long_distance(self, name, region):
  34. latest_region = self.__latest_region.get(name)
  35. return latest_region is None or latest_region != region
  36. class SmsSender(Observer):
  37. def update(self, observable, object):
  38. print('["短信发送"]' + object.get('name') + '你好!你的账户异常登录' + '登录ip:' + object.get('ip'))
  39. class MailSender(Observer):
  40. def update(self, observable, object):
  41. print('["短信发送"]' + object.get('name') + '你好!你的账户异常登录' + '登录ip:' + object.get('ip'))
  42. if __name__ == '__main__':
  43. account = Account()
  44. account.add_observer(SmsSender())
  45. account.add_observer(MailSender())
  46. account.login('Tony', '192.168.2.235', '12:30')
  47. account.login('Tony', '192.168.3.37', '12:30')

应用场景

对一个对象状态或者数据的更新需要其他对象同步更新,或者一个对象的更新需要依赖另一个对象更新。
对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节,如消息通知。