• 趣谈 Python 设计模式(1)观察者模式


    当您觉得自己的代码写的没有建筑美感,对各种发行源码的书写方式表示费解的时候。就大概可以判断您的编程水平在懂得语法的玩具阶段。您可能逻辑很清晰,各种函数、类、对象、包也使用很熟练。但是当您反复看语言文档的时候会发现,总有一部分语法好像从来都没有使用过,如装饰器、迭代器等等。
    这时候您所需要进阶的内容通常是这么几个关键词:元编程、设计模式、框架……
    希望您能写出大师级的代码,一起加油ヾ(◍°∇°◍)ノ゙


    观察者模式有两个角色组成观察者(observer)和被观察者(observable)。当您发现所面临的问题可以被抽象这两个角色的活动时,那么就可以按照此种设计模式来写代码。大师们已经对这类问题总结出了一套简洁的编程套路,我们只需要向这个套路中填空就好啦~

    我们来假设一个场景。社畜程序员的你晚上10点早早的下了班,赶上13号线地铁回到你一个月4000块的20㎡的小别墅里,想洗个热水澡。但是你上世纪的热水器并不能智能控温,烧的太久就会烫掉一层皮。你想发了工资换个智能热水器,但是房东说改动房价要交1W块的房屋改装费。无奈,你只能写个程序来控制你的破热水器。

    你想它有两个功能:1.水温达到50℃~70℃的时候,在屏幕上打印“可以洗澡啦!”;2. 水温达到100℃的时候,在屏幕上打印“可以喝热水啦!”

    不难发现,这里面有两个角色,一个有加热功能的热水器,一个在一旁观察温度等待输出字符串的某某某。所以我们先定义一个热水器类:

    class WaterHeater:
    
    	def __init__(self):
    		self.__temperature = 25
    	
    	def setTemperature(self, temperature):
    		"""这里用手动设置温度来简化模拟自动加热的过程"""
    		self.__temperature = temperature
    		print("The current temperature is: " + str(self.__temperature))
    
    	def getTemperature(self):
    		return self.__temperature
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    被观察者很简单就被初步定义好了,接下来看看观察者应该怎么定义。

    from abc import ABCMeta, abstractmethod
    
    class Observer(metaclass=ABCMeta):
    	
    	@abstractmethod
    	def update(self, waterHeater):
    		pass
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    先不用细究 metaclass=ABCMetaabstractmethod 前者可以理解继承,后者是将 update 方法声明为抽象方法。它只有一个需要传入被观察者实例化对象的方法 update。如果你是一个观察者的实例对象,你观察谁呢?肯定是观察被观察者的实例对象。你要观察他一点要能知道它的信息,所以一定要把他作为参数传进来。
    因为在观察者模式中,被观察者只有一个,但是观察者可以有很多个。所以 Observer 类是所有观察的模子。

    关于 @abstractmethod 的使用请自行查阅相关资料,关键词为:装饰器

    假设我们有两个观察者,一个关注洗澡,一个关注饮用。只需要继承 Observer 重写就好了:

    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("可以喝水啦!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    其实我们也完全可以使用一个观察者:

    class WashingDrinkingMode(Observer):
    	def update(self, waterHeater):
    		if waterHeater.getTemperature() >= 50 and waterHeater.getTemperature() < 70:
    			print("可以洗澡啦!")
    		if waterHeater.getTemperature() >= 100:
    			print("可以喝水啦!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    现在我希望通过这几个类定义来运作这个功能啦,请看代码:

    # 先实例化出观察者和被观察对象
    heater = WaterHeater()
    washingObser = WashingMode()
    drinkingObser = DrinkingMode()
    # 热水器开始加热,并保持观察
    heater.setTemperature(30)
    washingObser.update(heater)
    drinkingObser.update(heater)
    heater.setTemperature(60)
    washingObser.update(heater)
    drinkingObser.update(heater)
    heater.setTemperature(80)
    washingObser.update(heater)
    drinkingObser.update(heater)
    heater.setTemperature(100)
    washingObser.update(heater)
    drinkingObser.update(heater)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们貌似实现了要求的功能,但是这种方法就跟你自己站在那里盯着热水器的温度表没有区别呀。我想要的是在我随意执行 heater.setTemperature() 的时候,突然某一次我的屏幕上就有输出了。我不应该手动去调用观察者的 update 方法。

    不难想到,我应该让被观察者的属性变化(或者其他的什么变动后)自动的调用观察者的 update 方法。所以被观察者应该知道观察者的存在。所以观察者的实例对象,一定要成为被观察者的属性。于是修改一下代码:

    class WaterHeater:
    
    	def __init__(self):
    		self.__observers= []
    		self.__temperature = 25
    	
    	def setTemperature(self, temperature):
    		"""这里用手动设置温度来简化模拟自动加热的过程"""
    		self.__temperature = temperature
    		print("The current temperature is: " + str(self.__temperature))
    
    	def getTemperature(self):
    		return self.__temperature
    
    	def addObserver(self, observer):
    		self.__observers.append(observer)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个给被观察者添加了一个观察者列表属性self.__observers,并定义了 addObserver 方法,用于将观察者实例对象添加进列表中。此时采用下面的编写方法,被观察者就已经能够知晓都有哪些观察者了。

    # 先实例化出观察者和被观察对象
    heater = WaterHeater()
    washingObser = WashingMode()
    drinkingObser = DrinkingMode()
    # 将观察者添加进被观察者的属性列表中
    heater.addObserver(washingObser)
    heater.addObserver(drinkingObser)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现在只需要在温度发生改变的时候,调用观察者的 update 方法就可以了。

    class WaterHeater:
    
    	def __init__(self):
    		self.__observers= []
    		self.__temperature = 25
    	
    	def setTemperature(self, temperature):
    		"""这里用手动设置温度来简化模拟自动加热的过程"""
    		self.__temperature = temperature
    		print("The current temperature is: " + str(self.__temperature))
    		self.notifies()
    
    	def getTemperature(self):
    		return self.__temperature
    
    	def addObserver(self, observer):
    		self.__observers.append(observer)
    
    	def notifies(self):
    		for o in self.__observers:
    			o.update(self)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    增添了一个监听方法 notifies,他会遍历整个观察者列表来执行其 update 方法,由于观察者的 update 方法需要通过被观察者的信息来判断自己应该做什么,因此将观察者对象 self 作为参数传递进来。
    现在只需要在每次温度变化时 (执行 setTemperature),调用 notifies 方法就可以了。

    于是最终的运行程序为:

    # 先实例化出观察者和被观察对象
    heater = WaterHeater()
    washingObser = WashingMode()
    drinkingObser = DrinkingMode()
    # 将观察者添加进被观察者的属性列表中
    heater.addObserver(washingObser)
    heater.addObserver(drinkingObser)
    # 热水器开始加热,每次温度变化都会执行监听方法,通知各个观察者做出相应
    heater.setTemperature(30)
    heater.setTemperature(60)
    heater.setTemperature(80)
    heater.setTemperature(100)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    现在来对观察者模式做一下官方性总结。
    观察者模式就是在对象之间定义一种一对多的关系,可以有任意个观察者同时监听某一个对象。当被观察者在状态或者数据等你所关心的方面发生变化时,就会通知所有观察者对象,使他们做出相应的反应。


    下面我们来使用一下标准观察者模式模板,来重新编写上面的代码

    # 观察者模式的模型抽象
    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.__observers.remove(observer)
    
    	def notifyObservers(self, obj=0):	# obj 代表其他参数
    		for o in self.__observers:
    			o.update(self, obj)
    
    class WaterHeater(Observable):
    
    	def __init__(self):
    		super().__init__()
    		self.__temperature = 25
    	
    	def setTemperature(self, temperature):
    		"""这里用手动设置温度来简化模拟自动加热的过程"""
    		self.__temperature = temperature
    		print("The current temperature is: " + str(self.__temperature))
    		self.notifyObservers()
    
    	def getTemperature(self):
    		return self.__temperature
    
    class WashingMode(Observer):
    	def update(self, observable, obj):
    		if isinstance(observable, WaterHeater) \
    			and waterHeater.getTemperature() >= 50 and waterHeater.getTemperature() < 70:
    			print("可以洗澡啦!")
    
    class DrinkingMode(Observer):
    	def update(self, waterHeater, obj):
    		if isinstance(observable, WaterHeater) and waterHeater.getTemperature() >= 100:
    			print("可以喝水啦!")
    
    • 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

    观察者模式的使用要点

    • 明确谁是观察者,谁是被观察者。比如 windows 上的一个窗口是被观察者,会有鼠标点击的观察者、键盘输入的观察者、位置变动的观察者,等等。
    • 被观察者至少要有三种方法:添加、移除、监听。观察者至少要有一个更新方法。
    • 此外它也还有其他的一些名字,发布/订阅(publish/subscribe)模式、模型/视图(Model/View)模式、源/监听器(Source/Listener)模式、监听模式、从属者模式。在观察者模型中,方法命名通常为 addObserver/removeObserver;在源/监视器模型中,叫做 attach/detach;在桌面窗口开发中叫做 attachWindow/detachWindow。他们都是观察者模型!

    扩展知识:观察者模型有两种主要应用,推模型和拉模型。推模型中不管观察者是谁,都会给被观察者传递消息,如垃圾邮件推送服务。拉模型中,只会给观察者传递简单的信息,观察者看到消息时,根据需要主动向观察者获取更多的信息,如手机系统更新。
    上面的例子是两种模式都支持的,拉模型和推模型跟多的是操作上的小区别。如果是推模型,observer 可以是空,推送的消息全部通过 obj 传递;拉模型时,observerobj 都传递消息,或只有 observer 传递消息,需要更具体的信息的时候再通过 observer 去取。

  • 相关阅读:
    gitlab 部署
    五个超级实用的Python小技巧
    canvas画布绘图
    818专业课【考经】—《信号系统》之章节概要:第六章 连续时间系统的变换域分析
    安装RPM包或源码包
    deeplog中输出某个 event 的概率
    【Mysql】Mysql的数据类型
    YOLO物体检测-系列教程7:YOLOV3源码解读5之 Darknet
    智能合约自动化工具:示例指南
    Sui基金会举办了仪式并于9月底成功启动zkLogin
  • 原文地址:https://blog.csdn.net/curledgoat/article/details/126506533