目录
1.什么是监听模式
监听模式又名观察者模式,顾名思义就是观察者与被观察的关系。比如在烧开水的时候它开没开,你就是观察者,水就是被观察者,等等像这种的模式。
2.监听模式设计思想
观察者模式是对象的行为模式。
监听模式的核心思想就是在被观察者与观察者之间建立一种自动触发的关系。
监听模式是一种一对多的关系,可以有一个或者多个观察者对象同时监听某一个对象。监听的对象叫观察者(监听者),被监听的对象叫被观察者(Observable,也叫主题,即Subject)。被观察者对象在状态或内容(数据)发生变化时,会通知所有观察者对象,使它们能够做出相应的变化(如自动更新自己的信息)
3.监听模式的框架模型
from abc import ABCMeta, abstractmethod # 引入ABCMeta 和 abstractmethod 来定义抽象类和抽象方法 # metaclass=ABCMeta : 使创建的类为抽象类 class Observer(metaclass=ABCMeta): """观察者的基类""" @abstractmethod def updata(self, observalbe, object): pass class Observable: """被观察者的基类""" def __init__(self): self.__observers = [] # 添加观察者 def addObserver(self, observer): self.__observers.append(observer) # 删除观察者 def removeObserver(self, observer): self.__observers.remove(observer) # 内容或状态变化时通知所有的观察者 def notifyObservers(self, object=0): for o in self.__observers: o.updata(self, object)
Observable是被观察者的抽象类,Observer是观察者的抽象类。 另外还有三个方法,分别用于添加和删除观察者,notifyObservers 用于内容或状态变化时通知所有的观察者。因为Observable的notifyObservers会调用Observer的update方法,所有观察者不需要关心被观察者的对象什么时候会发生变化,只要有变化就会自动调用update,所以只需要关注update实现就可以了。
4.分别用框架模型和不用框架模型来解决下面的例子
简单的例子,比如热水器热水,到多少度可以饮用,到多少度可以洗澡的问题,
不用框架模型
from abc import ABCMeta, abstractmethod # 热水器类 class WaterHeater: def __init__(self): # 观察者的数量 self.__observers = [] # 设置初始温度 self.__temperature = 25 # 获取温度 def getTemperature(self): return self.__temperature # 设置温度 def setTemperature(self, temperature): self.__temperature = temperature print("当前的温度是", str(temperature), "℃") # 调用notifies方法,其实就是调用每个观察者中的update self.notifies() def addObserver(self, observer): self.__observers.append(observer) def notifies(self): for o in self.__observers: o.update(self, ) # 创建观察者的抽象类 class Observer(metaclass=ABCMeta): """观察者的基类""" @abstractmethod def update(self, observalbe, object): pass # 洗澡的类WashingMode继承父类Observer class WashingMode(Observer): # 重写父类中静态方法update def update(self, waterHeater, **kwargs): if 50 <= waterHeater.getTemperature() <= 70: print("水已经烧好,可以洗澡") # 喝水的类DrinkingMode继承父类Observer class DrinkingMode(Observer): # 重写父类中静态方法update def update(self, waterHeater, **kwargs): if waterHeater.getTemperature() >= 100: print("水已经烧开,可以饮用了") def testWaterHeatper(): heater = WaterHeater() # 创建热水器类的对象 washingObser = WashingMode() # 创建洗澡类的对象 drinkingObser = DrinkingMode() # 创建喝水类的对象 # 调用热水器对象的添加观察者(洗澡类对象)的方法 heater.addObserver(washingObser) # 调用热水器对象的添加观察者(喝水类对象)的方法 heater.addObserver(drinkingObser) # 设置各种温度 heater.setTemperature(40) heater.setTemperature(60) heater.setTemperature(100) # 调用测试方法 testWaterHeatper() """ 运行结果: 当前的温度是 40 ℃ 当前的温度是 60 ℃ 水已经烧好,可以洗澡 当前的温度是 100 ℃ 水已经烧开,可以饮用了 """
使用框架模型
# @Time : 2020/9/27 11:56 # @Author : GodWei # @File : observerPattern.py from abc import ABCMeta, abstractmethod, ABC # 引入ABCMeta 和 abstractmethod 来定义抽象类和抽象方法 # metaclass=ABCMeta : 使创建的类为抽象类 class Observer(metaclass=ABCMeta): """观察者的基类""" @abstractmethod def update(self, observable, object): pass class Observable: """被观察者的基类""" def __init__(self): self.__observers = [] # 添加观察者 def addObserver(self, observer): self.__observers.append(observer) # 删除观察者 def removeObserver(self, observer): self.__observers.remove(observer) # 内容或状态变化时通知所有的观察者 def notifyObservers(self, object=0): for o in self.__observers: o.update(self, object) class WasterHeater(Observable): """热水器的类,继承父类(被观察者的抽象类)""" def __init__(self): super().__init__() self.__temperature = 25 def getTemperature(self): return self.__temperature def setTemperature(self, temperature): self.__temperature = temperature print("当前的温度是", str(temperature), "℃") self.notifyObservers() class WashingMode(Observer): """洗澡的类,继承父类(观察者的抽象类)""" def update(self, observable, object): if isinstance(observable, WasterHeater) \ and 50 <= observable.getTemperature() <= 70: print("水已经烧好,可以洗澡") class DrinkingMode(Observer): """喝水的类,继承父类(观察者的抽象类)""" def update(self, observable, object): if isinstance(observable, WasterHeater) \ and observable.getTemperature() >= 100: print("水已经烧开,可以饮用") def testWaterHeatper(): heater = WasterHeater() # 创建热水器类的对象 washingObser = WashingMode() # 创建洗澡类的对象 drinkingObser = DrinkingMode() # 创建喝水类的对象 # 调用热水器对象的添加观察者(洗澡类对象)的方法 heater.addObserver(washingObser) # 调用热水器对象的添加观察者(喝水类对象)的方法 heater.addObserver(drinkingObser) # 设置各种温度 heater.setTemperature(40) heater.setTemperature(60) heater.setTemperature(100) # 调用测试方法 testWaterHeatper() """ 运行结果: 当前的温度是 40 ℃ 当前的温度是 60 ℃ 水已经烧好,可以洗澡 当前的温度是 100 ℃ 水已经烧开,可以饮用 """
结果是一样的,使用框架后,结构更新清晰,直接用观察者被观察者的父类继承即可
5.模型说明
(1)要明确谁是观察者谁是被观察者,问题也就明白了。一般观察者与被观察者之间是多对一的关系,一个被观察者对象可以有多个监听对象(观察者)。
(2) 被观察者至少需要有三个方法:添加监听者,移除监听者,通知Observer的方法。观察者至少要有一个方法:更新方法,即更新当前的内容,做出相应的处理
6.监听模式根据侧重可以分为推模型和拉模型
(1) 推模型:被观察者对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。一般在这种模型的实现中,会把观察者对象中的全部或部分信息通过update参数传递给观察者
(2) 拉模型: 被观察者在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到被观察者对象中获取,相当于观察者从被观察者对象中拉数据。一般在这种模型中,会把被观察者对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
7.账号异常登录检测和诊断机制
服务器会记录最近登录的时间、地区、IP地址等,从而知道常用的登录地区,如果检测出哪一次登录和平时的地区相差非常大,则认为是一次异常登录, 只要登录异常一出现就自动发送信息,短信和邮箱的发送机制我们就可以认为是登录的观察者。
import time from abc import ABCMeta, abstractmethod # 使用监听模式的框架 class Observer(metaclass=ABCMeta): """观察者的基类""" @abstractmethod def update(self, observable, object): pass class Observable: """被观察者的基类""" def __init__(self): self.__observers = [] # 添加观察者 def addObserver(self, observer): self.__observers.append(observer) # 删除观察者 def removeObserver(self, observer): self.__observers.remove(observer) # 内容或状态变化时通知所有的观察者 def notifyObservers(self, object=0): for o in self.__observers: o.update(self, object) class Account(Observable): """用户账户""" def __init__(self): super().__init__() self.__latestIp = {} self.__latestRegion = {} def login(self, name, ip, time): region = self.__getRegion(ip) if self.__isLongDistance(name, region): self.notifyObservers({"name": name, "ip": ip, "region": region, "time": time}) self.__latestRegion[name] = region self.__latestIp[name] = ip def __getRegion(self, ip): """由Ip地址获取地区信息,(只是进行模拟)""" ipRegions = { "117.23.33.34": "陕西省咸阳市", "66.117.31.255": "美国洛杉矶" } region = ipRegions.get(ip) if region is None: return "" else: return region def __isLongDistance(self, name, region): """计算本次登录与最近登录的地区差距(只是简单的进行模拟,用字符串匹配模拟) """ latestRegion = self.__latestRegion.get(name) if latestRegion is not None and latestRegion != region: return latestRegion class SmsSender(Observer): """短信发送器""" def update(self, observable, object): print("【短信发送】" + object["name"] + "您好!系统检测到您的账户可能登录异常。最近一次登录信息:\n" + " 登录地区:" + object["region"] + " 登录ip :" + object["ip"] + " 登录时间:" + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(object["time"]))) class MailSender(Observer): """邮件发送器""" def update(self, observable, object): print("【邮件发送】" + object["name"] + "您好!系统检测到您的账户可能登录异常。最近一次登录信息:\n" + " 登录地区:" + object["region"] + " 登录ip :" + object["ip"] + " 登录时间:" + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(object["time"]))) def testLogin(): accout = Account() accout.addObserver(SmsSender()) accout.addObserver(MailSender()) accout.login("GodWei ", "117.23.33.34", time.time()) accout.login("GodWei ", "66.117.31.255", time.time()) testLogin()
运行结果:
""" 【短信发送】GodWei 您好!系统检测到您的账户可能登录异常。最近一次登录信息: 登录地区:美国洛杉矶 登录ip :66.117.31.255 登录时间:2020-09-28 09:21:25 【邮件发送】GodWei 您好!系统检测到您的账户可能登录异常。最近一次登录信息: 登录地区:美国洛杉矶 登录ip :66.117.31.255 登录时间:2020-09-28 09:21:25 """
这段代码只是用来简单模拟程序,而且只记录了上一次的登录信息到Account对象中
实际项目中,像用户的信息,登录时间,ip,最近登录的情况,都存在数据库中。
8.应用场景
(1) 对一个对象状态或数据的更新需要其他对象同步更新,或者一个对象的更新需要依赖另一个对象的更新
(2) 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节,如消息推送
1.设计模式中监听模式(观察者模式)(Python实现)
最新推荐文章于 2024-08-21 08:04:33 发布