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のメソッドチェーンで、alicebobcharlie の順につなげます。

最後に alicedef supportclass Troubleを渡して実行すると、alicebobcharlie の順にサポートがたらいまわしされ、処理します。

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

--

--