本文首發(fā)于Gevin的博客
原文鏈接:Flask Signals 入門
未經(jīng) Gevin 授權(quán)襟诸,禁止轉(zhuǎn)載
1. 如何理解Flask Signals
Flask Signal非常簡(jiǎn)單易用,能大大降低代碼的耦合度仗考,也能夠讓基于Flask開發(fā)的系統(tǒng)更加健壯。然而,雖然Flask Signal上手很容易,但它卻不是Flask開發(fā)中一個(gè)入門級(jí)的功能夺颤,也不是每個(gè)開發(fā)者都會(huì)想到去使用的功能,這主要是由于不理解這個(gè)機(jī)制造成的谣殊。因此拂共,使用Flask Signal前,首先要理解Flask Signal是什么姻几。
什么是Signal?Flask官方文檔的描述的過于言簡(jiǎn)意賅势告,資深開發(fā)者能夠馬上明白蛇捌,而初學(xué)者恐怕摸不到頭腦,因此Gevin對(duì)官方文檔的描述做簡(jiǎn)單擴(kuò)展:Signal用于解耦系統(tǒng)的行為和業(yè)務(wù)邏輯咱台,這種解耦是通過當(dāng)某些行為被觸發(fā)時(shí)络拌,自動(dòng)發(fā)送定義好的一種信號(hào),與這個(gè)信號(hào)綁定的一些業(yè)務(wù)邏輯或行為回溺,接收到這個(gè)信號(hào)后春贸,會(huì)自動(dòng)執(zhí)行各自相應(yīng)的業(yè)務(wù)邏輯。與該信號(hào)綁定的業(yè)務(wù)邏輯遗遵,可以是事先預(yù)定義好的萍恕,也可以是在后續(xù)開發(fā)中隨需求變動(dòng)新增上去的,基于signal的機(jī)制车要,系統(tǒng)會(huì)更加穩(wěn)定和易于擴(kuò)展允粤,也使得系統(tǒng)的業(yè)務(wù)邏輯更加清晰 —— 當(dāng)某個(gè)行為觸發(fā)時(shí),發(fā)送一個(gè)信號(hào)即可,所有與該行為有關(guān)的業(yè)務(wù)邏輯或行為都會(huì)自動(dòng)觸發(fā)类垫,從而實(shí)現(xiàn)了解耦司光。換句話說,F(xiàn)lask Signal是觀察者模式的一種實(shí)現(xiàn)悉患,它使得我們開發(fā)的系統(tǒng)残家,更加符合開閉原則。
『設(shè)計(jì)模式』既是編碼典型問題的經(jīng)典解決方案售躁,也算是一種『行話』跪削,不是代碼初學(xué)者需要接觸的內(nèi)容,所以才導(dǎo)致了Flask Signal對(duì)資深開發(fā)者和初學(xué)者而言迂求,在理解上出現(xiàn)這么大的差異碾盐。理解了Signal的本質(zhì),使用就不是問題了揩局,如果Gevin的解釋還無法讓你理解毫玖,恐怕你要從『觀察者模式』入手了,但Gevin建議凌盯,沒有足夠的代碼積累付枫,不要過早接觸太多設(shè)計(jì)模式,否則無異于揠苗助長(zhǎng)驰怎。
2. Flask Signals的使用
Flask signal的使用非常簡(jiǎn)單阐滩,F(xiàn)lask 官方文檔把signal介紹的過于復(fù)雜,不利于入門县忌,在理解了signal后掂榔,只要學(xué)會(huì)以下幾點(diǎn)便可以輕松掌握signal的使用。
(1)Signal的創(chuàng)建
Signal的創(chuàng)建只需下面3行代碼即可完成:
from blinker import Namespace
my_signals = Namespace()
model_saved = my_signals.signal('model-saved')
(2)Signal的發(fā)送
Signal的發(fā)送通過成員函數(shù) send()
即可完成症杏,send()
函數(shù)的第一個(gè)參數(shù)為signal的sender装获,在class
中發(fā)送signal和在function
中傳遞的sender參數(shù)略有不同,class中sender=self
厉颤,function
中sender=current_app._get_current_object()
穴豫,即:
# In case send signal in a class:
class Model(object):
...
def save(self):
model_saved.send(self)
# In case send signal in a function:
def save_model():
...
model_saved.send(current_app._get_current_object())
sender()
函數(shù)除了sender
參數(shù)外,還可以添加多個(gè)可選參數(shù)逼友,這些可選參數(shù)是為signal的訂閱者使用的精肃,具體例子見下一小節(jié)。
(3)編寫Signal的訂閱者
Signal的訂閱者是一些訂閱了指定signal(如上文中的model_saved
)的函數(shù)帜乞,當(dāng)signal被激活(即調(diào)用send()
函數(shù)發(fā)送signal)時(shí)司抱,會(huì)自動(dòng)觸發(fā)這些訂閱者的調(diào)用,以完成某些功能挖函。將一個(gè)普通函數(shù)變?yōu)閟ignal的訂閱函數(shù)非常簡(jiǎn)單状植,只要加一個(gè)decorator
即可浊竟,仍以上面signal為例,定義一個(gè)訂閱者方法如下:
@model_saved.connect_via(app)
def on_model_saved():
# do something ...
對(duì)于大型項(xiàng)目津畸,代碼組織比較復(fù)雜振定,也許app
是在系統(tǒng)運(yùn)行時(shí)創(chuàng)建的,上面代碼中的app
可以用current_app._get_current_object()
取代肉拓。
除了connect_via
后频,connect
也可以,即:
@model_saved.connect
def on_model_saved():
# do something ...
如果訂閱者函數(shù)有參數(shù)暖途,需要在發(fā)送signal時(shí)卑惜,將相關(guān)參數(shù)作為signal.send()
函數(shù)的可選參數(shù)傳入,這樣訂閱者函數(shù)可以接收到相應(yīng)參數(shù)驻售,舉個(gè)詳細(xì)的例子:
from blinker import Namespace
from . import models
# Define a signal
octblog_signals = Namespace()
post_visited = octblog_signals.signal('post-visited')
# Define a subscriber
@post_visited.connect
def on_post_visited(sender, post, **extra):
tracker = models.Tracker()
tracker.post = post
...
tracker.save()
# Emit signal in a function
def post_detail(slug, is_preview=False):
post = models.Post.objects.get_or_404(slug=slug, post_type=post_type)
data['post'] = post
# do something
...
# send signal
if not is_preview:
signals.post_visited.send(current_app._get_current_object(), post=post)
...
總結(jié)
Flask Signal上手簡(jiǎn)單露久,功能強(qiáng)大,最關(guān)鍵的是它能夠解耦業(yè)務(wù)和行為欺栗,使代碼的邏輯更簡(jiǎn)潔毫痕,推薦使用。
稍后有時(shí)間我會(huì)在GitHub上放一個(gè)簡(jiǎn)單但完整的Flask Signal使用的小案例迟几,如果大家有好的應(yīng)用場(chǎng)景消请,歡迎給我留言~