GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Chain of Responsibility(チェーン オブ レスポンシビリティ・パターン)
「Chain of Responsibility」という英単語は、「責任の連鎖」を意味します。
このパターンは、ある要求の受取り対象となる複数のオブジェクトに鎖状の関係を構築し、要求を処理する事が可能なオブジェクトに渡るまで、順次、構築した鎖状の関係に沿って要求を受流していくパターンです。
このパターンを適用すると、「この要求はこのオブジェクトが処理する」などという司令塔的な役割り(結び付き)を利用者側が意識しなくて良くなり、利用者側は「連鎖関係にある任意のオブジェクトに要求を投げるだけ」、処理側は「流れてきた要求が自身で処理できる場合は処理し、できない場合は、その要求を次のオブジェクトに渡すだけ」という役割分担が明確となります。(利用者側は、処理の詳細まで意識する必要はない)
UML class and sequence diagram
UML class diagram
(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)
□ 備忘録
Chain of Responsibility
パターンは、昔のお役所仕事的な「たらい回し」な業務スタイルを連想できます。
人に要求がやってくる。その人がそれを処理できれば処理をする。処理できないなら、その要求を「次の人」にたらい回しする。次の人がそれを処理できるなら処理する。処理できなければ、その要求を「さらに次に人」にたらい回しする。
Chain of Responsibility
パターンは、所謂、線形リストのような処理モデルと理解しました。
■ "Chain of Responsibility"のサンプルプログラム
実際に、Chain of Responsibilityパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- トラブル解決に必要な案件に対して、
トラブル番号
を採番して、以下のトラブル解決のたらい回しな業務フローに沿って、トラブルを解決する - トラブルが解決した場合は、その解決結果を出力する
- トラブルが解決できなければ、最終的に、"解決できず"で終了する
<トラブル解決のたらい回しな業務フロー>
(1) 事前に、たらい回しの順番を決めておく("Alice"->"Bob"->"Charlie"->"Diana"->"Elmo"->"Fred")
(2) まずは、Alice
がトラブル解決の要求を受け付ける。Alice
は、サポートしない役割なので、次のBod
に要求をたらい回しする
(3) Bod
が要求を受け付ける。Bod
は、**制限付きサポート(Trouble番号の上限"100")**の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のCharlie
に要求をたらい回しする
(4) Charlie
が要求を受け付ける。Charlie
は、**特別サポート(Trouble番号が"429"のみ)**の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のDiana
に要求をたらい回しする
(5) Diana
が要求を受け付ける。Diana
は、制限付きサポート(Trouble番号の上限"200")の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のElmo
に要求をたらい回しする
(6) Elmo
が要求を受け付ける。Elmo
は、Trouble番号が奇数のみサポートの役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のFred
に要求をたらい回しする
(7) Fred
が要求を受け付ける。Fred
は、**制限付きサポート(Trouble番号の上限"300")**の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、最終的に、"解決できず"で終了する
$ python Main.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].
なんか、よくわかりづらい出力結果になりました。
サンプルプログラムの出力結果を理解するには、サンプルプログラムの詳細を確認する方が早そうです。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Chain_of_Responsibility
- ディレクトリ構成
.
├── Main.py
├── support.py
└── trouble.py
(1) Handler(処理者)の役
Handler
役は、要求を処理するインタフェースを定める役です。
「次の人」を保持しておき、自分で処理ができない要求がきたら、その人にたらい回しします。もちろん、「次の人」もHandler
役です。
サンプルプログラムでは、Support
クラスが、この役を努めます。
要求を処理するメソッドは、support
メソッドでした。
from abc import ABCMeta, abstractmethod
class Support(metaclass=ABCMeta):
def __init__(self, name):
self.__name = name
self.__next = None
def setNext(self, next):
self.__next = next
return next
def support(self, trouble):
if self.resolve(trouble):
self.done(trouble)
elif self.__next is not None:
self.__next.support(trouble)
else:
self.fail(trouble)
def __str__(self):
return "[{0}]".format(self.__name)
@abstractmethod
def resolve(self, trouble):
pass
def done(self, trouble):
print("{0} is resolved by {1}.".format(trouble, self))
def fail(self, trouble):
print("{0} cannot be resolved.".format(trouble))
(2) ConcreteHandler(具体的処理者)の役
ConcreteHandler
役は、要求を処理する具体的な役です。
サンプルプログラムでは、NoSupport
, LimitSupport
, OddSupport
, SpecialSupport
の各クラスが、この役を努めます。
class NoSupport(Support):
def __init__(self, name):
super(NoSupport, self).__init__(name)
def resolve(self, trouble):
return False
class LimitSupport(Support):
def __init__(self, name, limit):
super(LimitSupport, self).__init__(name)
self.__limit = limit
def resolve(self, trouble):
return True if trouble.getNumber() < self.__limit else False
class OddSupport(Support):
def __init__(self, name):
super(OddSupport, self).__init__(name)
def resolve(self, trouble):
return True if trouble.getNumber() % 2 == 1 else False
class SpecialSupport(Support):
def __init__(self, name, number):
super(SpecialSupport, self).__init__(name)
self.__number = number
def resolve(self, trouble):
return True if trouble.getNumber() == self.__number else False
(3) Client(依頼人)の役
Client
役は、最初のConcreteHandler
役に要求を出す役です。
サンプルプログラムでは、startMain
メソッドが、この役を努めます。
from support import NoSupport, LimitSupport, SpecialSupport, OddSupport
from trouble import Trouble
def startMain():
alice = NoSupport("Alice")
bob = LimitSupport("Bob", 100)
charlie = SpecialSupport("Charlie", 429)
diana = LimitSupport("Diana", 200)
elmo = OddSupport("Elmo")
fred = LimitSupport("Fred", 300)
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred)
for i in range(0, 500, 33):
alice.support(Trouble(i))
if __name__ == '__main__':
startMain()
(4) その他
トラブル番号を一元管理します。
class Trouble:
def __init__(self, number):
self.__number = number
def getNumber(self):
return self.__number
def __str__(self):
return '[Trouble {0}]'.format(self.__number)