前言
在web开发中, 你可能会遇到下面这种场景:
在用户完成某个操作后, 自动去执行一些后续的操作. 譬如用户完成修改密码后,
你要发送一份确认邮件.
当然可以把逻辑写在一起,但是有个问题是,触发操作一般不止一种(如用户更改了其它信息的确认邮件),这时候这个逻辑会需要写多次,所以你可能会想着DRY(Don’t repeat yourself),于是你把它写到了一个函数中,每次调用。当然这是没问题的.
但是, 如果你换个思路你会发现另一个完全不同的方案, 即:
- 类似于daemon的程序监听着特定的事件
- 前置操作来触发相应的事件
- 监听程序执行对应的操作
这样的好处是什么呢?
- 松耦合(不用把后续操作写在主逻辑中)
- 便于复用(这也是为什么django本身,及第三方应用如pinax大量使用此技术的原因)
在各种高级语言中都会有类似的特性,如java,javascript等,而在django中我们使用signal。
观察者模式
Siganl是Django框架中提供的一个 “信号分发器”。它是设计模式中经常提到的观察者模式的一个实现应用。
在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。
观察者模式的使用场景
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
它在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。这种耦合性使得代码的可读性、维护性大大提高。
观察者模式实现了动态联动;
由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态的控制注册的观察者来控制某个动作的联动范围,从而实现动态联动。
观察者模式支持广播通信。
目标发送通知给观察者是面向所有注册的观察者,所以目标每次通知的信息就要对所有注册的观察者进行广播,也可以在目标上添加新的方法来限制广播的范围。
Django 中Siganl 机制的典型应用是,框架为 Models 创建了 pre_save、post_save等与Model的某些方法调用相关联的信号,如pre_save 和 post_save 分别会在 Modle的save()方法的调用之前和之后通知观察者,从而让观察者进行一系列操作。
django signal的处理是同步的,勿用于处理大批量任务。
django signal对程序的解耦、代码的复用及维护性有很大的帮助。
Signal 机制的实现方式
Siganl的源码位于django dispatch包下,主要的代码位于 dispatcher.py中。
在dispatcher中定义了Signal类,以及一个用于使用Python装饰器的方式来连接信号以及信号接受者的方法receiver(signal,**kwargs)
。
class Signal(object):
"""
Base class for all signals
Internal attributes:
receivers
{ receiverkey (id) : weakref(receiver) }
"""
def __init__(self, providing_args=None, use_caching=False):
"""
创建一个新的Signal
providing_args 参数,指定这个Siganl 在发出事件(调用send方法)时,可以提供给观察者的信息参数
比如 post_save()会带上 对应的instance对象,以及update_fields等
"""
self.receivers = []
if providing_args is None:
providing_args = []
self.providing_args = set(providing_args)
self.lock = threading.Lock()
self.use_caching = use_caching
# For convenience we create empty caches even if they are not used.
# A note about caching: if use_caching is defined, then for each
# distinct sender we cache the receivers that sender has in
# 'sender_receivers_cache'. The cache is cleaned when .connect() or
# .disconnect() is called and populated on send().
self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
self._dead_receivers = False
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
from django.conf import settings
if dispatch_uid:
lookup_key = (dispatch_uid, _make_id(sender))
else</