オブジェクト指向における再利用のためのデザインパターン改訂版 読書ノート 3/3

著者:Eric Gamma, Richard Helm, Ralph Johnson, John Vlissides
監訳者:本位田真一、吉田和樹
出版社:ソフトバンク クリエイティブ株式会社
発行年:1999 年
ISBN:978-4-7973-1112-9

第 5 章 振る舞いに関するパターン

Chain of Responsibility

複数オブジェクトに要求を処理する機会を与える。要求の送信・受信オブジェクト間の結合を避け、オブジェクトをチェーンでつなぐ。

動機として、GUI アプリで状況依存ヘルプ機能について考察している。

  • ヘルプ情報は <もっとも特殊なものからもっとも一般的なものへと組織化していく> (p. 237)

    印刷ボタン→印刷ダイアログ→アプリケーション、のような順にヘルプ優先度がある感じがわかる。

  • <ヘルプ要求を出すオブジェクトをヘルプ情報を与えるオブジェクトから引き離す方法> (p. 237) が必要。

  • <このパターンのポイントは、複数のオブジェクトに要求を処理する機会を与えることにより、要求を送信するオブジェクトと受信するオブジェクトを引き離すことにある> (p. 237)

  • <チェーンの中にある各オブジェクトは、要求を処理したり後続のオブジェクト (successor) にアクセスするための共通のインタフェースを持っている> (p. 238)

  • ヘルプの要求を処理したいクラスは、HelpHandler を親クラスにする (p. 238)

結果

  • 結合度を低くする。
  • 責任分散を柔軟にする。
  • 要求が受信されるかどうかは保証されない。

実装

  • チェーンの実装
    • オブジェクト間に既存の参照関係があれば、それを流用して successor チェーンとすることもあり。
  • チェーンの接続
    • Handler クラスでは、通常は successor も保持する。要求を successor に転送することを実装のデフォルトとする。
  • 要求の表現
    • 要求を Request オブジェクトとしてカプセル化して HandleRequest に渡すのが普通。
    • <サブクラスは自らが関与すべき要求のみを扱う。他の要求は転送する> (p. 242)

サンプルコードではさっきのヘルプの実装を検討している。

  • <既存のウィジェット階層での親オブジェクトへの参照を利用する> (p. 243) ことで、ヘルプ要求を伝えていく方法をとる。
  • 「チェーンの最初」といったら、一番最初に要求を処理するチャンスのあるオブジェクトを指すようだ。
  • <ただし、successor は Widget オブジェクトではなく、任意の HelpHandler オブジェクトである> (p. 245)

使用例

  • <ユーザのイベントを処理するために Chain of Responsibility パターンが使われている> (p. 246)
  • <ユーザがマウスをクリックしたりキーを押したりすると、イベントが生成され、チェーンに沿って伝えられていくことになる> (p. 246)

関連するパターン

  • <しばしば Composite パターンとともに適用される。その場合、component の親オブジェクトを successor にすることができる> (p. 247)

    子(持たれている方)が先に要求を処理するチャンスがあって、無視するならば親(所有者)に転送、か。

Command

  • 要求をオブジェクトとしてカプセル化

  • 取り消し可能なオペレーションをサポート

  • <ツールキットはボタンやメニューの中で、直接、要求を実装することはできない> (p. 249)

  • <要求自身をオブジェクトにすることにより、ツールキットのオブジェクトが、仕様化されていないアプリケーションオブジェクトの要求を作成できるようにする> (p. 249)

  • <もっとも簡単な形としては、このインタフェースに、抽象化した Execute オペレーションを入れておく> (p. 249)

  • <Command の具象クラスが受信オブジェクトとアクションの組を明らかにする。受信オブジェクトには、要求を実行するために必要な知識がある> (p. 249)

    PasteCommand の場合、受信オブジェクトは Document である。

  • <command をつなぎ合わせるのは一般的なので、 MenuItem オブジェクトが複数の command に対して Execute オペレーションの呼び出しを行うことができるように、 MacroCommand クラスを定義する> (p. 251)

    図を見ればこれがすぐに Composite パターンだとわかる。

  • 動的に command を入れ替えることができるということが、状況依存のヘルプを実装するのに便利。

  • command を複数合成することで、コマンドスクリプトをサポートすることができる。

適用可能性

  • <Command パターンでは、そのようなコールバック関数の代わりにオブジェクトを使う> (p. 251)

    なるほど。Command パターンはコールバック関数の進化系なんだろう。

  • <command での処理の結果を再び元の状態に戻すことができるように、状態を保存するようにしておくことができる> (p. 252)

    • <呼び出し結果を元に戻す Unexecute オペレーション> (p. 252)
    • 実行 command を履歴リストに蓄えておき、 <取り出しや再実行は、このリスト内を前後に移動しながら> (p. 252) Execute/Unexecute の連続呼び出しで Undo/Redo を実装できる、か。
  • <一般に、1 つのトランザクションは、データに対する更新手続きの集合をカプセル化している> (p. 252)

結果のリストを見ると、このパターンにはデメリットがないようだ。

実装

  • <極端な場合、単に Receiver オブジェクトと要求を実行するアクションを結び付けるだけのものから、 Receiver オブジェクトにはまったく委譲することなくそれ自身ですべてを実装してしまうものまで考えることができる> (p. 254)

    さっきの例で言うと PasteCommand::ExecuteDocument::Paste メソッドを呼び出すだけか、貼り付けロジックを PasteCommand が独自に実装するかの違いが考えられるということ。

  • Undo/Redo をサポートする場合、 <Receiver オブジェクトは、自身を元の状態に戻すことができるようなオペレーションを command に対して提供しなければならない> (p. 254)

    • 履歴リストも必要。リスト内を時系列順に移動するような。

    • <たとえば、選択された複数のオブジェクトを削除する DeleteCommand オブジェクトは、それが実行される際には、削除されるオブジェクトの集合を保持しておかなければならない> (p. 255)

    • ヒステリシス。 Undo/Redo を反復実行すると、何かがまずくて元の状態とは異なってくる様。

      <command が他のオブジェクトの内部に踏み入ることなくこの情報にアクセスできるように、 Memento パターンを適用することができる> (p. 255)

サンプルコード

  • <取り消しできない、または引数を必要としない簡単な command については、 Receiver オブジェクトをパラメータ化するためにクラステンプレートを使うことができる> (p. 257)

    // p. 257
    template <class Receiver>
    class SimpleCommand : public Command{
    public:
        typedef void (Receiver::*Action)();
    
        SimpleCommand(Receiver*, Action);
    
        // ...
    };
    

    ただし、このクラスのコンストラクタ呼び出しはコードを書くのが面倒。

  • MacroCommand クラスに Unexecute オペレーションを実装する場合、逆順に command をたどって Unexecute を実行しなければならない (p. 258)

  • <command を削除するのは MacroCommand クラスの責任である> (p. 258)

Interpreter

真面目に読む気なし。

運用適用性

  • <文法が単純な場合> (p. 263)
  • <効率が重要な関心事ではない場合> (p. 263)

結果

  • <文法が複雑なときには、パーザやコンパイラジェネレータのような他の技術を使うほうが適当だろう> (p. 265)

実装

  • <Interpreter パターンと Composite パターンは、実装上の問題において多くの共通点を持っている> (p. 265)

サンプルコード

  • <C++ で実装された Bool 表現を操作・評価するシステム> (p. 269) の例。これは先に利用例を見てから、各メソッドを見ていくのが理解がいいと思う。

    // p. 272 一部改変
    
    VariableExp* x = new VariableExp("X");
    VariableExp* y = new VariableExp("Y");
    BooleanExp* expression = new OrExp(
        new AndExp(new Constant(true), x),
        new AndExp(y, new NotExp(x)));
    
    Context context;
    context.Assign(x, false);
    context.Assign(y, true);
    bool result = expression->Evaluate(context);
    
  • <Interpreter パターンには、Composite パターンを使ったクラス階層上に 1 つのオペレーションを分散させる、ということ以上の意味がある> (p. 273)

使用例

  • <オブジェクト指向言語により実装されたコンパイラでは広く使われている> (pp. 273-274)
  • <もっとも一般的な形式(つまり、1 つのオペレーションを Composite パターンに基づくクラス階層上に分散させるような場合)を考えると、 Composite パターンはほとんどの場合に Interpreter パターンを含んだ形で使われる。しかし Interpreter パターンは、クラス階層を言語を定義するものとして考えた場合に限り使用すべきである> (p. 274)

Iterator

動機

  • <リストのような集約オブジェクトは、その内部構造を明かすことなく、要素にアクセスする方法をユーザに対して提供するべきである> (p. 275)

  • <アクセスや走査のための責任を抜き出して、これを iterator オブジェクトに与えるということである> (p. 275)

  • <走査のメカニズムを List オブジェクトから切り離すことで、 List インタフェースを使って要素を列挙していく以外にも、走査について異なる方針を持った iterator を定義することができるようになる> (p. 276)

  • <CreateIterator オペレーションは、factory method の使用例になる> (p. 276)

    クラス図によると AbstractList のメソッドに CreateIterator がある。 ListListIterator を返し、 SkipListSkipListIterator を返す。

結果

  • <複雑な aggregate には、走査の方法がたくさんあるだろう> (p. 278)
  • <コード生成では、構文解析木を inorder に走査することもあれば、 preorder に走査することもあるだろう> (p. 278)
  • <複数の走査を同時に実行することができる> (p. 278) とあるが、そういうケースを何か例示して欲しい。

実装

  • <外部 iterator は内部 iterator に比べてより柔軟である> (p. 278)

    iteration を制御するのがクライアントか iterator 自身かの違いによって、外部 iterator と呼んだり内部 iterator と呼んだりするようだ。

  • <aggregate が走査のアルゴリズムを定義していて、 iterator は単に iteration の状態を保持しておくためだけに使われるのかもしれない> (p. 279)

  • <走査の最中に aggregate に要素を追加したり、また削除したりすると、ある要素に 2 回アクセスしてしまったり、またはまったくアクセスしなかったりということが起こりかねない> (p. 279)

    これはよくミスるんだ。

  • <iterator には特権的なアクセス権を持たせてもよい> (p. 280)

  • <外部 iterator を、Composite パターンで示されるような再帰的な集約構造上で実装するのは難しいだろう> (p. 280)

    • <構造内でのパスを保存しておかなければならない> (p. 280)
    • 構造内のノードが兄弟、親、子ノードをたどれる場合は、 cursor ベースの iterator のほうがよい。
    • <composite の構造は、しばしば 2 種類以上の方法で走査する必要がある。 preorder, postorder, inorder, breadth-first などの走査が一般的である> (p. 281)

関連パターン

  • <iterator は、iteration の状態を把握するために memento を使うことができる> (p. 290)

Mediator

  • 相互作用をカプセル化する。 <オブジェクト同士がお互いを明示的に参照し合うことがないように> (p. 291)

  • <オブジェクト間の関連を増やすことがせっかく高めた再利用性を再び低める傾向がある> (p. 291)

  • <しばしば、ダイアログ内のウィジェット間には依存関係がある> (p. 292)

  • <別のダイアログボックスでは、ウィジェット間に異なる依存関係が存在するだろう> (p. 292)

  • 例えば FontDialogDirector クラスを定義し、それに <ウィジェット間の通信におけるハブ> (p. 292) として活躍させる。

    • ウィジェットは他のウィジェットのことを知っている必要がなくなる。
  • <オブジェクトの集まりが通信する場合> (p. 294) に Mediator パターンを適用する可能性がある。

  • <mediator 自体を保守が難しい一枚岩> (p. 296) になる。

  • colleague から mediator への通信手段だが、 <1 つのアプローチとしては、Observer パターンを使って Mediator クラスを Observer として実装することがあげられる> (p. 296)

  • <Mediator パターンのもう 1 つの適用例として、複雑な更新を調整する場合があげられる。例としては、Observer パターンで説明する ChangeManager クラスがあげられる。(略) ChangeManager オブジェクトは、変化が起こったオブジェクトに対して依存関係にあるオブジェクトにそれを知らせることにより更新を行う> (p. 300)

    オブジェクト間の依存関係が複雑な場合の更新調整という意味だろうか。

Memento

  • オブジェクトを後にある時点の状態に戻すことができるようにするというパターン。

  • <memento は、別のオブジェクトの内部状態のスナップショットを保存するオブジェクトである> (p. 304)

    • <別のオブジェクト> のことを memento に対して originator と呼ぶ。
    • originator は要求に応じて memento を返す。
  • <Caretaker クラスには、Memento クラスの narrow インタフェースが見えるようになっている> (p. 305)

    とにかく Caretaker オブジェクトは、Memento オブジェクトの中身を細かくいじるようなことはない。

  • <それとは対称的に、Originator クラスには wide インタフェースが見えるようになっている> (p. 305)

    Memento オブジェクトを生成する役割があるから、 Originator は Memento のことをよく把握している必要がある。

  • <理想的には、Memento オブジェクトを生成した Originator オブジェクトだけが Memento オブジェクトの内部構造にアクセスすることを許されるようにする> (p. 305)

  • Memento のデータ量が多いケースでは、コストが高くつく。「差分」だけを保存しておけば済むようにできるなら、そうする。

  • Caretaker は Memento を管理する。オブジェクトを削除する責任がある (p. 307) ということ。

Observer

<あるオブジェクトが状態を変えたときに、それに依存するすべてのオブジェクトに自動的にそのことが知らされ、また、それらが更新されるように、オブジェクト間に一対多の依存関係を定義する> (p. 313)

  • <関連するオブジェクト間で無矛盾性を保つ必要がある> (p. 313)
  • しかし、そのためにクラス間の結合度を高めるようなことはしたくない (p. 313)
  • スプレッドシートとバーチャートの例え (p. 313) は、 <同じデータに対して異なるユーザインタフェースがいくつあっても構わない> (p. 314) ということを示したい。
  • <subject には、それに依存する observer を任意の数だけ持たせることができる> (p. 314) ということは、極端な話ゼロでも構わない(意義があるかどうかは置いて)。

適用可能性のところに色々書いてあるが、基本的には <1 つのオブジェクトを変化させるときに、それに伴いその他のオブジェクトも変化させる必要があり、しかも変化させる必要があるオブジェクトを固定的に決められないとき> (p. 314) 状況で決まりだろう。

  • subject は observer を知っている。
  • observer は更新のインタフェースを定義する。
  • ConcreteSubject は ConcreteObserver に影響する状態を保存している。
  • ConcreteObserver は ConcreteSubject への参照を保持している。
  • <通知を得るまでには自身の状態の更新を延ばしている> (p. 316)
  • <subject と observer の結合は抽象的であり極小である> (p. 316)
  • <observer 同士は互いに相手の存在を知らないため、 subject の変化に伴うコストの総計を observer が予測することはできない> (p. 316)

このパターンは記述量がけっこうある。

  • <subect が多くて observer が少ないときにはコストが高くつく> (p. 317)
  • 1 つの observer が複数の subject に依存しているような場合、 <どの subject が通知を送ったのかを observer に知らせるように Update オペレーションインタフェースを拡張する必要がある> (p. 317)
  • <どのオブジェクトが Notify オペレーションを呼び出すことになるのか> (p. 317) だが、subject にやらせるにせよ observer にやらせるにせよ、トレードオフがある。
  • <subject が削除される際に、observer に対して subject への参照をリセットするように通知を出すようにすること> (p. 317) を検討する。
  • <Subject クラスのどのオペレーションが通知のきっかけを作るのかは、文書化しておくのがよい> (p. 318)
  • subject の変更情報をどのように observer に引き渡すかで、 push 型と pull 型に分類できる。これもトレードオフがある (pp. 318-319)
  • subject と observer の依存関係が複雑なときには、間にワンクッション ChangeManager オブジェクトのようなものをはさんで、依存関係や変更通知を管理させる場合がある (pp. 319-320)

サンプルコードは「時計」の実装例。タイマーが Subject で、各種時計が Observer だ。

State

  • <クラス内では、振る舞いの変化を記述せず、状態を表すオブジェクトを導入することでこれを実現する> (p. 325)

  • TCPConnection の例では、 established, listen, closed の状態をそれぞれクラスとして表現する。

    <このパターンでキーとなる考え方は、 TCPState クラスと呼ばれる抽象クラスを導入することである> (p. 325)

  • パターン適用可能性としては、 <オペレーションが、オブジェクトの状態に依存した多岐に渡る条件文を持っている場合> (p. 326) 等がある。

  • Context クラスが State オブジェクトを持つ。

    • <状態に依存した要求を ConcreteState オブジェクトに委譲する> (p. 327)
    • <ConcreteState オブジェクトに対して自身を引数として送る> (p. 327)
  • 状態遷移は Context クラスか ConcreteState クラスが決定するらしい (p. 327)

    どちらでも OK ということか。

結果

  • <個々の状態に対する振る舞いを State のサブクラスに分配するため、クラスの数は増え、1 つのクラスを利用する場合よりもコンパクトではなくなるという問題である。しかし、多くの状態が存在する場合には、このように分配することにより、実際に良い効果が得られる。なぜならば、もしこの方法を用いていなければ、多数の条件文が必要になるからである> (p. 327)
  • <実行状態という概念をオブジェクトの地位にまで引き上げる> (p. 327)
  • <Context クラスが矛盾した内部状態を持つのを防ぐことができる> (p. 328)
  • もし ConcreteState が独自の変数を持たないならば、Flyweight パターンも検討。

実装

  • <どの構成要素が状態遷移の規準を定義するのかを特定していない> (p. 328)
    • <State のサブクラス自身が次の状態と遷移の時期を特定できれば、一般的により柔軟で適切なものになる> が、サブクラス間に依存関係が入り込むことになる。
  • テーブル検索型の状態遷移も紹介していて、利点と欠点を挙げている。決定的な欠点は遷移の規準が不明確になることだろうか。
  • Context クラスがどの程度の頻度で状態を変えるかによって、 ConcreteState オブジェクトの生成・破棄の戦略を決めるのがよい。
  • <委譲ベースの言語> (p. 329) とは何だろう。

サンプルコード

  • TCPConnection

    • TCPState を friend 宣言している。
    • void ChangeState(TCPState*) メソッドを提供する。
    • この例の運用では、コンストラクタで TCPClosed に状態メンバーをセットする。
  • TCPState

    • 多くの TCP 関連メソッドは空実装。当然仮想関数。

    • ここにも ChangeState という名のメソッドがいる。

      // p. 331
      void TCPState::ChangeState(TCPConnection* t, TCPState* s){
          t->ChangeState(s);
      }
      
  • ConcreteState

    • この例ではいずれも <ローカルな状態> を保持しないので、各サブクラスを Singleton とする。

    • 例えば TCPListen::Send メソッドの実装は、まず SYN や ACK の送受信処理を行ってから、最後に

      ChangeState(t, TCPEstablished::Instance());
      

      のようにする。

使用例

  • <インタラクティブ描画プログラム> における <ツール> について説明。

    <たとえば、線描画ツールは、新しい線を生成するためにユーザにクリックとドラッグを行わせる。選択ツールは、ユーザに図形を選択させる> (p. 333)

    Tool のサブクラスとして線描画ツールやら選択ツールやらが定義されていて、クリックやドラッグのアプリケーション内での振る舞いが実際にはサブクラスに委譲されているので、それぞれ異なる。

Strategy

  • Strategy はカプセル化された交換可能なアルゴリズム (p. 335)
  • 別名が Policy になっている。

テキストストリームを取り扱う方法を例に話が進む。

  • <特に、改行について複数のアルゴリズムをサポートする場合> (p. 335)
  • <テキストをフォーマットするときには、Compositor のオブジェクトに対してこの責任を委譲する> (p. 336)

適用可能性も色々挙げているが、基本はこれだろう。

  • 多くの振る舞いが <複数の条件文として現れている場合> (p. 336)

構造、構成要素、協調関係について。

  • Strategy がアルゴリズムに共通のインタフェースを宣言する。
  • ConcreteStrategy がアルゴリズムを実装する。
  • Context が Strategy を利用する。アルゴリズムに必要なデータを引き渡したりするのかもしれない。
  • 色々なアルゴリズムをサポートするのに Context を派生させない理由は、 <アルゴリズムの実装と Context クラスの実装が混ざってしまい、 Context クラスを理解し、保守し、拡張することをより難しくしてしまう> (p. 338) から。わざわざアルゴリズムを独立させている。
  • <振る舞いの種類がクライアントに関係がある場合にのみ、 Strategy パターンを利用するべきである> (p. 339)
  • Context は ConcreteStrategy が効果的にアクセスできるようにするべし (p. 339)
  • C++ の場合、テンプレートを利用して Strategy をコンパイル時に選択させることができる (p. 340) もっとも、Strategy を動的に変更できなくて構わない場合に限る手段だが。

サンプルコードの Compose メソッドは引数リストがゴチャゴチャしてないか?

Template Method

  • <アルゴリズムのスケルトン> (p. 347)
  • <その中のいくつかのステップについては、サブクラスの定義に任せることにする> (p. 347)

またぞろ Application と Document クラスの例を挙げ、 Document を「開く」オペレーションについての議論。

// pp. 347-348; 一部省略
void Application::OpenDocument(const char* name){
    if(!CanOpenDocument(name)){
        return;
    }

    Document* doc = DoCreateDocument();
    if(doc){
        _docs->AddDocument(doc);
        AboutToOpenDocument(doc);
        doc->Open();
        doc->DoRead();
    }
}
  • <OpenDocument オペレーションは、文書を開くための各ステップを定義する> (p. 348)

    OpenDocument はおそらく仮想関数になっていなくて、この中の各呼び出しメソッドが Application や Document の仮想関数になっている。 <抽象オペレーションを使ってアルゴリズムのいくつかのステップを定義することにより、 template method はそれらの順番を固定する> (p. 348)

適用可能性にいいことが書いてある。

  • <まず、既存のコードにおける相違点を識別し、次にその相違点を新しいオペレーションに分離する。最後に、既存のコードを、その相違点については新しいオペレーションを呼び出すようにした template method で置き換える> (p. 348)

このセクションは短い。

  • <template method は、コード再利用のための基本的な方法である> (p. 349)
  • ハリウッドの原則
  • <hook operation は、デフォルトでは何もしないようにしておくことがしばしばある> (p. 350)

実装のコツ

  • C++ では
    • primitive operation を private 宣言する (p. 351)
    • <template method は非仮想関数として宣言しておく> (p. 351)
  • primitive operation の数を最小化すること (p. 351)
  • 名前を見て template method, primitive operation とわかるようにすると便利 (p. 351)

<template method はたいへん基本的なもの> (p. 352)

Visitor

  • 最初に読んだときにクラス構造が頭にストンと入らなかったパターン。
  • <オペレーションを加えるオブジェクトのクラスに変更を加えずに、新しいオペレーションを定義することができる> (p. 353)

動機

  • 構文木の例

    • Node のサブクラスに VariableRefNodeAssignmentNode 等がある。

    • <数多くのノードのクラスにわたってこれらのオペレーションを分散させることが、システムを理解しにくく、保守しにくく、変更しにくくしてしまう> (p. 353)

      各ノードに TypeCheck やら GenerateCode が分散している。三重苦状態なわけだ。

    • <そこで、関連するオペレーションを各クラスから取り出して別のオブジェクトにまとめ、アブストラクト・シンタックスツリーを走査するときにその要素にこのオブジェクトを渡す、というアプローチをとることができる> (p. 354)

      • 別のオブジェクトが visitor と呼ばれるもの。 TypeCheckingVisitor やら CodeGeneratingVisitor やらだ。
  • <Visitor パターンでは、2 つのクラス階層を定義する> (p. 355)

    • オペレーションを加えられる側 (Node)
    • オペレーションを定義する側 (NodeVisitor)

適用可能性

  • 適用条件の記述が割と細かい。
  • <オブジェクト構造を定義するクラスはほとんど変わらないが、その構造に新しいオペレーションを定義することがしばしば起こる場合> (p. 355)

構成要素

  • ConcreteVisitor クラスは <構造を走査していく過程で、状態に結果が蓄積されていくことがしばしばある> (p. 356)
  • Element クラスは <引数として visitor をとる Accept オペレーションを定義する> (p. 356)
  • ConcreteElement クラスが Accept の実装をする。

結果

  • <Visitor パターンでは、Element の新しいサブクラスを加えることを難しくする> (p. 358)

  • <新しい ConcreteElement クラスがひんぱんに追加されるときには、 Visitor クラスの階層を保守することが難しくなる危険性がある> (p. 358)

  • <visitor によるアプローチでは、ConcreteElement クラスのインタフェースが、 visitor が仕事を行うのに十分、強力であることを仮定している> (p. 359)

    Element 側のカプセル化がもろくなる可能性を指摘している。

実装

  • 初見ではコード例が頭に入らなかった。練習問題として、

    • Visitor
    • ElementA, ElementB
    • ConpositeElement

    クラスを定義してみよう。 CompositeElement::Accept の実装方法に注意。

  • ダブルディスパッチの話題が出てくる。Accept がそれなのだが、

    • <ダブルディスパッチとは、単に、実行されるオペレーションが要求の種類と 2 つの受け手の型に依存することを意味している> (p. 361)

      Visitor の型と Element の型が Accept を決める。

サンプルコード

  • <visitor は通常 composite と関連がある> (p. 362) というわけで Composite パターンの説明で出てきた Equipment クラスを引っ張り出す。

    • <Equipment クラスはとても簡単なので、実際には Visitor パターンを利用する必要はない> (p. 362)
  • Visitor の利用例コードを見落としがちだが、以下のようになる。

    Equipment* component;
    
    // ... component をどこからか得る。
    
    InventoryVisitor visitor;
    component->Accept(visitor);
    // visitor 内部に Inventory 情報が蓄積された。
    

    単に Accept 呼び出しだけだ。

使用例

  • <Inventor では、3 次元のシーンをノードの階層として表現する。それぞれのノードは、幾何学的な図形オブジェクト、あるいはその属性のどちらかを表現している。シーンを描写したり入力イベントをマッピングするオペレーションは、それぞれ別の方法でこの階層を走査する必要がある> (p. 366)

    描画、イベント処理、検索、バウンディングボックス計算等々、それぞれの用途に専用の visitor が存在すると言っている。

まとめ

まだ全部読み切っていない。

  • Observer パターンでは <observer と subject が制約を維持するために協力し合わなければならない> (p. 370)
  • <mediator を再利用可能なものにするよりも、 observer と subject を再利用可能なものにする方が容易なのは明らかである> (p. 370)
  • <Observer パターンよりも Mediator パターンの方が通信の流れを理解するのは容易である> (p. 370)
  • <協力し合うオブジェクトが直接お互いを参照しているときには、(略)システムの階層化と再利用性に対してマイナスの効果を及ぼす> (p. 371)
  • <Observer パターンは、Command パターンよりも送信―受信オブジェクトの結合をさらにゆるく定義する> (p. 371)
  • <mediator は、さらに柔軟性を得るためには独自のディスパッチスキーマを実装しなければならないだろう> (p. 372)

第 6 章 終わりに

  • <本書は単に既存の設計法について述べたものである。本書はチュートリアルとしては妥当だが、熟練したオブジェクト指向設計者にはあまり役に立たないと思われるかもしれない> (p. 375)
  • <読者がいかにデザインパターンを見つけてカタログ化していくことができるかについて述べる> (p. 375)

6.1 デザインパターンに何を期待するか

  • <デザインパターンを用いることで、より高いレベルで設計し、設計について議論することが可能になるのだ> (p. 376)
  • <十分に長い間オブジェクト指向システムに従事すれば、自力でデザインパターンを習得することができるだろう。しかし、本書を読めばはるかに速く習得できるはずである> (p. 376)
  • <デザインパターンは分析モデルから実装モデルへの転換のときに特に効果がある> (p. 377)
  • <柔軟で再利用可能な設計には、分析モデルには存在しないオブジェクトが含まれる> (p. 377)
  • <進化を続けるためには、ソフトウェアは“リファクタリング”と呼ばれるプロセスによって作り直さなければならない> (p. 378)
  • <優秀は設計者はリファクタリングが必要になるような変更には気付くものである> (p. 378)

6.2 経緯

  • <本書のカタログは Erich の学位論文の一部として始まった> (p. 378) おお、学位論文なのか。
  • <しかし、パターンを理解できるのは、すでにパターンを使ったことのある人に限られていた> (p. 379)
  • <なぜ行っているのかを理解することは、何をしているかを理解するよりも難しい> (p. 379) これはいい言葉だ。

付録以降

ノートに取るほどの重大な記述はなさそうだ?