勉強日記

チラ裏

GoF本 Decorator

ねらい

  • オブジェクトに責務を動的に追加する

AKA

  • Wrapper
    • AdapterもWrapperであるが、大きく異なる
      • 最後の「関連するパターン」にて

モチベーション

  • クラス全体にではなく、個々のオブジェクトに責務を追加したいことがある
  • 継承で実現するのはイマイチ
    • 静的に責務が決まってしまい、柔軟でない
    • 責務の組み合わせにしたがい継承していくとサブクラス数が爆発する
  • 集約で実現するのがよい
    • 透過的なオブジェクトで包む
    • クライアントや包まれるオブジェクトは「透過的なオブジェクト」を意識しない
      • もちろん包む処理自体は除く

つかいどころ

  • 個々のオブジェクトに責務を動的に追加したい
  • 責務がオプショナル
  • 責務の追加を継承で実現するのが困難なケース
    • クラス数の爆発が予想される
    • クラス定義が隠されている

構造

f:id:wand_ta:20181216154155p:plain

登場人物

  • Component
    • インタフェース
    • 包まれるやつ
    • 責務の追加対象
  • ConcreteComponent
    • Componentを実装するクラス
  • Decorator
    • Componentを実装する抽象クラス
    • 包むComponentオブジェクトへの参照を保持
    • すべての操作についてデフォルト動作を定義
      Componentオブジェクトに処理を委譲する
      • Transparent Enclosure
  • ConcreteDecorator
    • Decorator派生
    • 責務を追加する
      • 操作をオーバライドし、前もしくは後に処理を追加する

クライアントコードからの利用

  • Decoratorでデコレートされていようがいまいが、Componentオブジェクトとして扱える(透過性)

功罪

  • 継承よりも柔軟
    • クラス数の爆発を回避できる
    • 同じデコレーションを複数回適用できる
      • BorderDecoratorを2回適用することで、2重ボーダーを適用できる
      • 継承で実現しようとすると、同じクラスを2回継承することになり、おそらくエラーになる
  • 機能盛りだくさんなクラスがCompositeクラスツリーの根っこ側に来るのを避けられる
    • 継承で実現しようとすると、追加されうるすべての機能を、ツリーの根っこ側で定義しなければならない
    • 結果、機能盛りだくさんで複雑なクラスができてしまう
      • ある機能の追加のために、関係ない機能が影響をうけがちになる
    • Decoratorで実現すれば、単機能のシンプルなDecoratorクラスに切り分けることができる

  • Decoratorオブジェクトで包んだオブジェクトはComponentとして透過的に扱えるが
    包む前のComponentオブジェクトと同一のオブジェクトではないことに注意
    • 安易に == 等で比較できなくなる
  • 小さなDecoratorオブジェクトが大量に生まれる
    • しかも全部似ている
      • 基本的に処理を委譲する
      • 機能を追加する部分だけ、前後で何かする
    • 不慣れな人のアサイン障壁になる
      • 【所感】勉強してくれ

実装にあたり考えるべきこと

  • DecoratorComponentインタフェースに準拠すること
    • これはextendsとかimplementsとかで実現する
  • 抽象クラスDecoratorを省く
    • ComponentインタフェースをConcreteDecoratorが直接実装する
      • 「処理を委譲するデフォルト動作」もConcreteDecoratorに書くことになる
    • ConcreteDecoratorが一つしかないならこれでいい
  • Componentインタフェースをシンプルに
    • データを保持すべきではない
      • 【所感】だからわざわざ本記事では「インタフェース」と書いている
    • データ保持はComponent実装クラスに任せる

Strategyパターンとの対比

f:id:wand_ta:20181216154315p:plain
Decorator

f:id:wand_ta:20181216154327p:plain
Strategy

  • Decoratorは表面を変える
    • ComponentはDecoratorに包まれることを認知しない
  • Strategyは中身を変える
    • ComponentはStrategyのことを知っていて、処理をStrategyオブジェクトに委譲する
  • Componentが巨大で、Transparent Enclosureを作るのがコストオーバーな場合は、Strategyパターンのほうが適切
    • 【所感】PHPなんかだとマジックメソッド__call()があるから楽ですね

関連するパターン

  • Adapter
    • ともに「Wrapper」として知られるが、大きく異なる
      • Decorator: 透過的=インタフェースを変えない、責務を追加する
      • Adapter: インタフェースを変える
  • Composite
    • Decoratorは、子ノードを1つしかもたない特別なCompositeパターンであるといえる
    • ただし、力点は異なる
      • Composite: 部分-全体構造を作ること
      • Decorator: 責務を追加すること
  • Strategy
    • Decoratorがオブジェクトの「表面を変える」のに対して、Strategyは「中身を変える」
    • 適材適所

英語

  • liability
    • (benefitsと対になることば)
      • 「責任」と訳されるが、負い目、負債といった負の側面が強調されるっぽい
  • proliferate
    • 増殖
      • クラス数が爆発するときなどにつかう
        • an explosion of subclasses
      • 本文では出てこなかったが、他のパターンの説明でしょっちゅう出てくる単語
  • error-prone
    • エラーになりがちな
  • feature-laden
    • 機能が盛りだくさんな
  • pay-as-you-go
    • 使った分だけ払う方式の
      • 本文では機能が必要になった文だけ実装する、という意味合いで使われている
      • Incremental
  • comformance
    • 準拠
      • インターフェースが合致している、という文脈で使用
  • gut
    • (skinと対になる言葉)
      • 表面/中身といった訳が適切か
  • adorn
    • decorateと同義
    • embellishとも一緒に出てきた
  • full-fledged
    • 一人前の
      • 羽が全部生えそろっていることを意味する
      • 病気やスキャンダルが進展・発展した際にも使われる
      • 本文では、Componentインターフェースの膨大なシグネチャを全部implementした、といった意味で使われている