02 设计模式之监听者模式

设计模式之监听者模式

1. 场景模拟(拉模型)

  • 监控温度,当温度到达一定程度时,会及时通知发出警告
  • 洗澡模式,饮用模式是监听者,热水器是被监听对象,热水器温度发生变化,监听者可以做出相应的判断
# 引入 定义抽象类和抽象方法
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(self.__temperature) + "℃")
        self.notifies()

    def addObserver(self, observer):
        self.__observers.append(observer)

    def notifies(self):               # 通知
        for o in self.__observers:    # 两个实例化对象在里面,分别调用自身的update方法,然后比较温度
            o.update(self)

class Observer(metaclass=ABCMeta):
    """洗澡模式和饮用模式的父类"""

    @abstractmethod
    def update(self, waterHeater):
        pass

class WashingMode(Observer):
    """该模式用于洗澡"""
    def update(self, waterHeater):
        if waterHeater.getTemperature() >= 50 and waterHeater.getTemperature() <= 70:
            print("水已经烧好了,温度正好,可以用来洗澡了")

class DrinkingMode(Observer):
    """该模式用于饮水"""
    def update(self, waterHeater):
        if waterHeater.getTemperature() >= 100:
            print("水已经烧好了,可以饮用了")

def testWaterHeater():
    # 实例化对象
    heater = WaterHeater()                # 加热器
    washingObser = WashingMode()
    print(washingObser)
    drinkingObser = DrinkingMode()
    print(drinkingObser)
    # 添加监听者
    heater.addObserver(washingObser)
    heater.addObserver(drinkingObser)
    # 设置温度
    heater.setTemperature(40)
    heater.setTemperature(60)
    # heater.setTemperature(100)

testWaterHeater()

2. 监听模式-代码框架

  • Obsevervable是被观察者的抽象类,Observer是观察者的抽象类。addObserver,removeObserver用于添加和删除观察者,notifyObsever用于内容或状态变化时通知所有的观察者,因为Obsevervable的notifyObsever会调用 Observer的update方法,所有的观察者不需要关心对象什么时候会发生变化,只要有变化就会自动调用update,所以只需要关注update就好。
    image
from abc import ABCMeta, abstractmethod

class Observer(metaclass=ABCMeta):
    """观察者基类"""
   @abstractmethod
    def update(self, observable, object):
        pass

class Observable:
    """被观察者基类"""
    def __init__(self):
        self.__observer = []

    def addObserver(self, observer):
        self.__observer.append(observer)

    def removeObserver(self, observer):
        self.__observer.remove(observer)

    def notifyObservers(self, object=0):
        for o in self.__observer:
            o.update(self, object)

3. 框架改进后代码(拉模型)

from abc import ABCMeta, abstractmethod

class Observer(metaclass=ABCMeta):
    """观察者基类"""
    @abstractmethod
    def update(self, observable, object):
        pass

class Observable:
    """被观察者基类"""
    def __init__(self):
        self.__observer = []

    def addObserver(self, observer):
        self.__observer.append(observer)

    def removeObserver(self, observer):
        self.__observer.remove(observer)

    def notifyObservers(self, object=0):
        for o in self.__observer:
            o.update(self, object)


class WaterHeater(Observable):
    """热水器"""
    def __init__(self):
        super().__init__()
        self.__temperature = 25

    def getTemperature(self):
        return self.__temperature

    def setTemperature(self, temperature):
        self.__temperature = temperature
        print("当前温度:" + str(self.__temperature) + "℃")
        self.notifyObservers()

class WashingMode(Observer):
    """该模式用来洗澡"""
    def update(self, observable, object):
        if isinstance(observable, WaterHeater) \
            and observable.getTemperature() >= 50 and observable.getTemperature() <= 70:   # 拉模型应用
            print("水已经烧好了,可以洗澡了")

class DrinkingMode(Observer):
    """该模式用来饮水"""
    def update(self, observable, object):
        if isinstance(observable, WaterHeater) \
            and observable.getTemperature() >= 100:
            print("可以洗澡了")

def testWaterHeater():
    heater = WaterHeater()
    washingObser = WashingMode()
    drinkingObser = DrinkingMode()
    heater.addObserver(washingObser)
    heater.addObserver(drinkingObser)
    heater.setTemperature(40)
    heater.setTemperature(60)
    heater.setTemperature(100)

testWaterHeater()

自己对监听者模式的疑问:在实现update时,不知道waterHeater/obsevrvable为什么就是被监督者的实例化对象?

4. 登录异常应用场景(推模型)

from abc import ABCMeta, abstractmethod
import time

"""
应用场景:账户异常登录时,会以短信或邮件的方式将登录信息(登录时间、地区、ip)发给绑定邮箱、手机
"""
class Observer(metaclass=ABCMeta):
    """观察者基类"""
    @abstractmethod
    def update(self, observable, object):         # 推拉模型
        pass

class Observable:
    """被观察者基类"""
    def __init__(self):
        self.__observer = []

    def addObserver(self, observer):
        self.__observer.append(observer)

    def removeObserver(self, observer):
        self.__observer.remove(observer)

    def notifyObsevers(self, object):
        for o in self.__observer:
            o.update(self, object)

class Account(Observable):
    """用户账户"""
    def __init__(self):
        super().__init__()
        self.__latestIp = {}          # 最新ip
        self.__latestRegion = {}      # 最新区域

    def login(self, name, ip, time):
        region = self.__getRegion(ip)        # 通过ip解析到登录位置(ip地址解析服务)
        if self.__isLongDistance(name, region):
            self.notifyObsevers({"name": name, "ip": ip, "region": region, "time": time}) # 推模型应用
        # 添加用户新登录的ip和地址到字典中
        self.__latestRegion[name] = region
        self.__latestIp[name] = ip
        print(self.__latestRegion, self.__latestIp)

    def __getRegion(self, ip):
        # 通过ip地址获取地区信息。 这里只是模拟,真实项目中应该调用ip地址解析服务
        ipRegion = {
            "101.17.18.9": "浙江杭州",
            "67.218.47.69": "美国洛杉矶"
        }
        region = ipRegion.get(ip)
        return "" if region is None else region          # 三元  返回的是城市

    def __isLongDistance(self, name, region):            # 判断是否是长距离
        # 计算本次登录与最近几次登录的地区差距
        # 这里只是用简单的字符串匹配模拟,真实项目调用地理信息相关服务
        latestRegion = self.__latestRegion.get(name)
        print(region)
        print(latestRegion)
        return latestRegion is not None and latestRegion != region      # true or false

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("wesley", "101.17.18.9", time.time())    # 第一次只是为了记录用户在那登录,不会跳出异常信息
    accout.login("wesley", "67.218.47.69", time.time())   # 第二次才会与第一次区域比较,不是原来地区就会有异常

testLogin()

5. 推拉模型

"""
推模型:
	被观察者对象向观察者推送主题的详细信息,不管观察者是否需要,推送信息通常是主题对象的全部或者部分数据,这种模型在现实中,会把被观察者对象中的全部或者部分信息通过update参数产地给观察者(通过update中的object对象)

拉模型:
	被观察者通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到被观察者对象中获取,相当于观察者从被观察者对象中拉数据(通过update中observable对象调用被观察者中的数据,object=0)
"""
posted @ 2021-08-24 21:40  超暖  阅读(482)  评论(0)    收藏  举报