「処理のたらい回しをする」パターンです。
たらい回しと聞くと非効率にも見えますが、「役割分担が明確となる」という点が明確になるメリットがあります。
GUIのソフトでイベントが起きている状況を想定します。
Press: key
Click: mouse
Timeout: timer
→ 方針として、
TimerHandler → KeyHandler → MouseHandler という順で処理をたらい回しをします。
なお、本例題は実践Python3を簡易化したものです。
events = ["timer", "key", "mouse", "key", "mouse", "terminate"]
def main():
print("Handler Chain #1")
handler1 = TimerHandler(KeyHandler(MouseHandler(NullHandler())))
i = 0
while True:
event = events[i]
if event == "terminate":
break
handler1.handle(event)
print("---") # => 処理の切れ目用
i += 1
class NullHandler:
def __init__(self, successor=None):
self.__successor = successor
def handle(self, event):
if self.__successor is not None:
self.__successor.handle(event)
class MouseHandler(NullHandler):
def handle(self, event):
print("(MouseHandler)")
if event == "mouse":
print("Click: {}".format(event))
else:
super().handle(event)
class KeyHandler(NullHandler):
def handle(self, event):
print("(KeyHandler)")
if event == "key":
print("Press: {}".format(event))
else:
super().handle(event)
class TimerHandler(NullHandler):
def handle(self, event):
print("(TimerHandler)")
if event == "timer":
print("Timeout: {}".format(event))
else:
super().handle(event)
if __name__ == "__main__":
main()
TimerHandler → KeyHandler → MouseHandlerという順で処理が行われ、
各条件にマッチするとHandlerがログを出してくれるのがわかると思います。
上のサンプルコードの内、大事な部分を抽出しました。
class NullHandler:
def __init__(self, successor=None):
self.__successor = successor
def handle(self, event):
if self.__successor is not None:
self.__successor.handle(event)
class KeyHandler(NullHandler):
...
class TimerHandler(NullHandler):
def handle(self, event):
print("(TimerHandler)")
if event == "timer":
print("Timeout: {}".format(event))
else:
super().handle(event)
if __name__ == "__main__":
handler2 = TimerHandler(KeyHandler(...))
handler2.handle("timer")
ポイントは2点
① Handlerを作る時にsuccessorを定義している
handler2 = TimerHandler(KeyHandler(...))
引数として与えることで、successorとして定義しています。 (定義はNullHandlerのinitの部分)
② super().handle(event)
によって、①で定義したsuccessorのhandleを動かしている
class TimerHandler(NullHandler):
def handle(self, event):
...
super().handle(event)
このように書くことで、親クラス(NullHandler
)のhandleを動かし、
親クラスのhandleが①で定義したsuccessorのhandleを動かします。
コルーチンによる書き方があります。
コルーチンはざっくり言うと「関数の途中で処理を止め(再開する)」ことができる機能です。
よくわからないのでサンプルコードを書きます。
# 参考: https://qiita.com/koshigoe/items/054383a89bd51d099f10
import functools
def main():
receiver = test_coroutine()
# next(receiver) # coroutine関数が代理でやってくれている
receiver.send(10)
receiver.send(20)
receiver.send(30)
def coroutine(func):
@functools.wraps(func)
def wrapper(*args, **kargs):
g = func(*args, **kargs)
next(g)
return g
return wrapper
@coroutine
def test_coroutine():
while True:
print("---")
v = yield
print("v: {}".format(v))
if __name__ == "__main__":
main()
大事なポイントは
yield
文で関数の処理が止まっていることsend(引数)
で関数の処理を再開でき、yieldの部分がsend
の引数になること
</span>の2点です。これを活かしてChain of ResponsibilityパターンをPythonicに書き換えます。
import functools
EVENT = ["timer", "key", "mouse", "key", "mouse", "terminate"]
def main():
pipeline = key_handler(mouse_handler(timer_handler()))
i = 0
while True:
event = EVENT[i]
if event == "terminate":
break
pipeline.send(event)
print("---")
i += 1
def coroutine(function):
@functools.wraps(function)
def wrapper(*args, **kargs):
generator = function(*args, **kargs)
next(generator)
return generator
return wrapper
@coroutine
def mouse_handler(successor=None):
while True:
event = (yield)
if event == "mouse":
print("Click: {}".format(event))
elif successor is not None:
print("(Click: pass)")
successor.send(event)
@coroutine
def key_handler(successor=None):
while True:
event = (yield)
if event == "key":
print("Press: {}".format(event))
elif successor is not None:
print("(Press: pass)")
successor.send(event)
@coroutine
def timer_handler(successor=None):
while True:
event = (yield)
if event == "timer":
print("Timeout: {}".format(event))
elif successor is not None:
print("(Timeout: pass)")
successor.send(event)
if __name__ == "__main__":
main()
大事な部分を抽出しました。
def main():
pipeline = key_handler(mouse_handler(timer_handler()))
...
pipeline.send(event)
...
@coroutine
def key_handler(successor=None):
while True:
event = (yield)
if event == "key":
print("Press: {}".format(event))
elif successor is not None:
print("(Press: pass)")
successor.send(event)
...
key_handlerはdecoratorにより、
同じ仕組みがmouse_handlerやtimer_handlerにも施されているため、
pipeline.send(event)によって、eventをたらい回すことができるようになっています。
関数しかないため省略。