现在模拟一个场景:你是个高中生,有4个小弟A、B、C、D,分别擅长语文、数学、英语、物理,在每次考试中,他们都正好坐在你后面一排,你根据不同的科目把试卷传给相应的小弟,他就会帮你答题,就像下面这样:
这种方式的特点是:(1) 你得记住每个小弟擅长哪一科;(2)你和每个小弟都得有联系。放到代码里,“你”和“小弟”之间的耦合度就太高了,如果小弟流动性比较高,岂不是要经常修改“你”,如果项目大一点儿,这种维护工作是很费力不讨好的。
我们用责任链来优化一下,哪有老大这么操心的,这种事完全可以交给秘书去做嘛,你可以从四个小弟中挑一个秘书(比如你看上了A),以后不管考什么,你都只管把卷子递给A,他如果处理不了,就转交给B,以此类推,直到有人能处理或所有人都处理不了。
或者再优化一下,添加两个专职秘书DebugHandler(放在责任链的开始,专门负责传递试卷并写日志)和FinalHandler(放在责任链末尾,如果有未处理的任务,就抛异常)。为了使用尽量简单的代码说明问题,下文中的代码均只包含图中的实线路径。
我们观察一下上图,责任链模式的优缺点就显而易见了:
有了思路,代码就好说了,常规责任链是酱婶儿的,要说的都在注释里了:
class BaseBro:
"""小弟的基类,将小弟共同的功能提取出来
"""
def __str__(self):
"""演示传递过程用,你也可以给每个类定义一个名字属性"""
return '竟然没人会做,丢掉它'
def __init__(self, successor=None):
"""在初始化时可以指定继任者(链条的下一个节点)。
如果没指定,那就是最后一个节点
"""
self._successor = successor
def handle(self, paper):
"""将试卷传递下去。
每个小弟都有自己的handle,如果自己处理不了,就调用这个基类的handle传递试卷。
"""
if self._successor is not None:
self._successor.handle(paper)
class ABro(BaseBro):
"""小弟A,会做语文"""
def __str__(self):
return 'A'
def handle(self, paper):
if paper == '语文':
print('小弟A把语文试卷做完啦~')
else:
print('A -->', str(self._successor))
super().handle(paper)
# 其他小弟功能完全一样,只是科目不一样,代码略,对应的类名分别为:BBro, CBro, DBro
# 创建链条,小弟的顺序无关紧要
bro = ABro(BBro(CBro(DBro(BaseBro()))))
# 开始考试(map返回值是Iterator,需要包一层list才能执行)
list(map(bro.handle, ['物理', '历史', '数学', '语文', '英语']))
打印结果如下,注意第二科历史,谁都不会做,就什么都没做:
如果你的项目用到了异步,我们也可以改造一下,基于协程实现一个异步责任链(关于协程,请参考《廖雪峰的官方网站》)。这里只对协程做个极简的介绍。
协程就是一个特殊的生成器,它比生成器多出如下两个特点:
定义一个无限循环的生成器好说,那怎么让生成器自己前进到首个yield表达式呢?当然你可以在用协程之前手动next(generator)
一下,启动协程,我们这儿稍微高级一点儿,让协程自己启动,可以定义一个修饰器:
P.S 先不考虑3.4引进的asyncio和3.5引进的async/await,在说明一个问题的时候尽量不引入额外的概念,想着怎么把协程说明白就够头疼了。
def coroutine(func):
"""
首先你得保证func是个无限循环的生成器。
然后这个修饰器可以帮你启动生成器使其变成协程。
"""
@functools.wraps(func)
def wrapper(*args, **kw):
generator = func(*args, **kw)
next(generator)
return generator
return wrapper
先捋一下思路:责任链的关键在于处理和传递。
yield
和send(value)
是关键,yield
可以接收由自己.send(value)
发过来的值,在一个协程内调用下个节点.send(value)
可以向下个节点传递值。一条天然的管道有木有!还是老样子,理清了思路,代码就简单了,想说的都在注释里:
# coroutine修饰器就是上小节中的代码
@coroutine
def a_bro(successor=None):
"""小弟A: 会语文"""
while True:
# yield负责接收数据
paper = (yield)
if paper == '语文':
print('--> A:做语文')
elif successor is not None:
# send()负责发送数据
print('A:不会做 -->')
successor.send(paper)
else:
print('A: 没下一个小弟了,扔掉它~')
# 其他小弟的代码逻辑一样,只是科目不一样,略。。。
# 创建链条,顺序无关紧要
pipeline = a_bro(b_bro(c_bro(d_bro())))
# 考试啦,通过pipeline.send()给协程发送数据
list(map(pipeline.send, ['物理', '数学', '语文', '历史', '英语']))
有没有发现,代码清爽了很多,不用在定义传送机制了(常规责任链中的基类),协程天然可以传送接收数据。执行结果如下,同样注意试卷的传递过程,历史没人会做,就直接丢掉了:
(全文完)
参考:
(1)《Python编程实战-运用设计模式、并发和程序库创建高质量程序》
(2)《JavaScript设计模式与开发实战》
2024 - 快车库 - 我的知识库 重庆启连科技有限公司 渝ICP备16002641号-10
企客连连 表单助手 企服开发 榜单123