概念解析
监听模式(观察者模式),核心是设计监听者和被观察者,当被观察者对象在状态或者内容数据发生变化时,能够通知所有的监听者对象(被动/主动)相应的变化,然后监听者对象能够做出相应的反应。其本身属于“一对多”的关系,也有很多名称:“发布/订阅模式”、“模型/视图模式”、“源/监听器模式”等。监听者模式的类图如下: 上图中, Subject
为被观察者抽象类, Observer
为监听者抽象类, ConcreteObserver
为监听者具体实现, ConcreteSubject
为被观察者具体实现。其中被观察者的核心方法是:添加监听者、移除监听者、通知监听者的方法。而监听者至少存在的一个方法:更新方法,做出相应处理。 设计上的细节:
- 设计过程要注意抽象出:谁是观察者和被观察者
- 被观察者(Subject)在通知的时候,不需要指定监听者,监听者可以自主订阅
设计模板
监听模式的代码框架如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from abc import ABCMeta, abstractmethod
# 引入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.__observer.remove(observer)
def notifyObservers(self, object=None):
for o in self.__observers:
o.update(self, object)
实例分析
登陆异常的检测和提醒,实现的功能如下:当账号异常登陆的时候,能够以短信/邮件形式将登陆信息发送给对应的号码和邮箱。
代码实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import time
# 导入时间处理模块
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地址获取地区信息。这里只是模拟,真实项目中应该调用IP地址解析服务
ipRegions = {
"101.47.18.9": "浙江省杭州市",
"67.218.147.69":"美国洛杉矶"
}
region = ipRegions.get(ip)
return "" if region is None else region
def __isLongDistance(self, name, region):
# 计算本次登录与最近几次登录的地区差距。
# 这里只是简单地用字符串匹配来模拟,真实的项目中应该调用地理信息相关的服务
latestRegion = self.__latestRegion.get(name)
return latestRegion is not None and latestRegion != region;
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("Tony", "101.47.18.9", time.time())
accout.login("Tony", "67.218.147.69", time.time())