pythonによるデザインパターン[Chain of Responsibility]
# Intro
この文章は結城 浩さんの「Java言語で学ぶデザインパターン入門」を、pythonで実装してみたサンプルコードです。
筆者の環境は以下の通りです。Python 3.6.3
まだ修行中の身なので間違いがあると思いますがご了承ください。 今回は「chain of responsibility」です。長い名前ですね。
# Summarise
本書のp210によると、 chain of responsibilityとは
複数のオブジェクトを鎖(チェーン)のようにつないでおき、そのオブジェクトの鎖を順次渡り歩いて、目的のオブジェクトを決定する方法
とあります。長い説明ですが、要は機能をたらい回しにつなげて、処理ができないなら、後ろの処理に回していく、と言ったイメージです。
# Practice
## Specification
トラブルが発生し、それをサポートセンターで解決するプログラムを作成します。
具体的にはトラブルをclass Trouble
で表し、各トラブルにはトラブル番号iが定められています。それを以下の4つのサポートセンターで解決します。
- 何もサポートしない
class NoSupport
- limitで指定したトラブル番号未満のものを解決
class LimitSupport
- 奇数のトラブル番号のトラブルを解決
class OddSupport
- 特定のトラブル番号を解決
class SpecialSupport
## Explanation
最初にトラブルを表す class Trouble
を作成します。生成するときにfieldとしてnumber(トラブル番号)を設定するだけで、特に難しいことはありません。
class Trouble:
def __init__(self, number):
self.number = number def __str__(self):
return '[Trouble {}]'.format(self.number)
次にサポートを束ねる抽象クラスの class Support
を作成します。
重要なのはfieldとしてnext
をとり、委譲(delegation)によって次のサポート(class Suport
)を参照することです。そしてdef set_next
によって、fieldを一つずつずらし、次のサポートを返します。
また、 def resolve
は抽象メソッドになっています。よって class Support
を継承したサブクラスで
- 奇数のトラブル番号のトラブルを解決
class OddSupport
- 特的のトラブル番号を解決
class SpecialSupport
などの具体的な解決方法を、def resolve
として定めます。
def support
では以下の順で処理を行います。
- もし
def resolve
(具体的な実装はサブクラス)で解決できたらdef done
(サポート完了)を実行 - もし次のサポートが存在したら次のサポートに
def support
を再帰的に実行(たらい回し) - 上記が満たされなかったら
def fail
(失敗)
最後に具体的なサポートを実装します。今回は4つのサポート方法がありますが実装方法は中身を見ればわかるので、奇数のトラブル番号のトラブルを解決class OddSupport
だけ説明します。
生成時に親クラスをよんで name
をセットし、 def resolve
では引数のトラブル番号が奇数の時のみ True
を返します。
main関数では以下のように記述します。
まずそれぞれのサポートを生成します。今回は
- 何もサポートしない
class NoSupport
をもつAlice
- トラブル番号100未満のものを解決する
class LimitSupport
を持つBob
- トラブル番号429を解決する
class SpecialSupport
を持つCharlie
を作成します。
そして alice
からdef set_next
のメソッドチェーンで、alice
→bob
→charlie
の順につなげます。
最後に alice
の def support
にclass Trouble
を渡して実行すると、alice
→bob
→charlie
の順にサポートがたらいまわしされ、処理します。
alice = NoSupport('Alice')
bob = LimitSupport('Bob', 100)
charlie = SpecialSupport('Charlie', 429)alice.set_next(
bob
).set_next(
charlie
)alice.support(Trouble(429))
## Sample Code
python3 chain_of_responsibility.py[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].
# Conclusion
委譲(deligation)とメソッドチェーンを使って、たらい回しが実現できましたね。
このデザインパターンを使わないと、例えば class Support
中にif分(switch文)をたくさん描いて、それぞれのトラブル番号に合わせて具体的なサポートを条件分岐しなくてはいけません。それでは拡張性も低く、条件分岐の処理が複雑になる可能性があるので良い構成ではありませんね。
さらに新しくサポートを追加したい時も class Support
を継承したクラスを付ければいいだけなので便利ですね。
ただし注意しなければならないのは、最初から順に処理を回しているので速度が遅くなる点です。要求と処理の関係が予め決まっており、速度が求められている場面ではchain of responsibilityは使わない方が良いかもしれません。(p220)。
# ref
- 「増補改訂版 Java言語で学ぶデザインパターン入門」 結城浩(著)第11版