読者です 読者をやめる 読者になる 読者になる

Compositeパターン


Rubyによるデザインパターン』(ラス・オルセン著 ピアソン・エデュケーション刊)をPythonで書いています。


目次



ある要素には幾つかの子要素がり、子要素には数個の孫要素がり、
さらに孫要素には・・・であって、


しかも、子も孫もひ孫も、同じようなインターフェイスを備えている、


と、いう構造は良くあります。



例えば、ディレクトリ構造など。


フォルダの中には子フォルダがあり、その子フォルダの中にもまたフォルダがあり・・・。


しかも、どの階層でも『フォルダ』である事には変わりはありません。


このような構造を実現するのが、Compositeパターンです。




ここでは、ケーキを作る工程を考えます。

製品の「ケーキを作る工程」には「生地を作る」「焼く」などの工程が、
「生地を作る」工程には「粉をボウルに入れる」「液体を加える」などの工程があります。


各工程は共通のインターフェイスとして、所要時間を返すget_time_requiredメソッドを備えます。

class Task(object):
    u"""ケーキ作りの工程"""
    def __init__(self, name):
        self.name = name
        self.parent = None #(オプション)自分の「親」工程
    
    def get_time_required(self):
        return 0.0

class CompositeTask(Task):
    u"""いくつかの子工程からなる工程"""
    def __init__(self, name):
        Task.__init__(self, name)
        self.sub_tasks = []
    
    def get_time_required(self):
        return sum(t.get_time_required() for t in self.sub_tasks)
    
    def add_sub_task(self, task):
        self.sub_tasks.append(task)
        task.parent = self #(オプション)子タスクの親は自分
    
    def remove_sub_task(self, task):
        self.sub_tasks.remove(task)
    
    def __getitem__(self, i):
        return self.sub_tasks[i] # (オプション)添え字で子工程にアクセスできるようにする

class MakeBatterTask(CompositeTask):
    def __init__(self):
        CompositeTask.__init__(self, "生地を作る")
        self.add_sub_task(AddDryIngredientsTask())
        self.add_sub_task(AddLiquidesTask())
        self.add_sub_task(MixTask())
    
class MakeCakeTask(CompositeTask):
    def __init__(self):
        CompositeTask.__init__(self, "ケーキを作る")
        self.add_sub_task(MakeBatterTask())
        self.add_sub_task(FillPanTask())
        self.add_sub_task(BakeTask())
        self.add_sub_task(FrostTask())
        self.add_sub_task(LickSpoonTask())


Compositeパターンの注意点

幸いな事に、Compositeパターンには犯しやすい間違いが1つしかありません。

そして悪い事に、多くの人がその間違いをよく犯します。



ツリーの深さが1段しかない、
つまりコンポジットオブジェクトの子コンポーネントが全て葉オブジェクトである
と想定してしまうのです。



ケーキ製造プロセスに、いくつの葉の工程(こねる・焼くなど、それ以上分解できない工程)がいくつあるかを数えるとします。

#
# 間違った方法
#
class CompositeTask(Task):
    def total_number_basic_task(self):
        return len(self.sub_tasks)


これだと、小工程がさらに孫工程からできている時に困ります。

#正しい方法
class Task(object):
    def total_number_basic_task(self):
        return 1

class CompositeTask(Task):
    def total_number_basic_task(self):
        return sum(t.total_number_basic_task() for t in self.sub_tasks)


不都合な違い

Compositeパターンには、一つの決断をしなくてはいけません。

つまり、葉のオブジェクトでadd_sub_taskやremove_sub_taskをどう扱うかです。



  1. 子を扱わないのだから、add_sub_task等は実装しない

  2. add_sub_task等を呼び出そうとしたら例外を投げる

  3. add_sub_task等を呼び出しても無視する



Compositeパターンのゴールは、全階層のオブジェクトを同じように扱えるようにする事です。


しかし、葉とコンポジットオブジェクトは同じではないのです。


Rubyによるデザインパターン』の作者は1.(実装しない)が良いという意見です。