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

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

第 3 章 生成に関するパターン

  • <クラス継承よりもオブジェクトコンポジションに頼る形でシステムを発展させていく場合に、生成に関するパターンは重要になる> (p. 89)
  • MazeGame::CreateMaze についての考察が数ページ続くが、高密度な記述ゆえに上手い形にノートにまとめられない。
    • 2 つの部屋からなる簡単な迷路を作っているだけなのに、コードが複雑 (p. 92)
    • 迷路構成を変更しようとすると、メンバ関数のオーバーライド(実質再定義)か、それと同等の仕事が必要となる (p. 92)
  • <より柔軟な設計(必ずしも、コードを短くするわけではない)> (p. 93) このカッコ内がポイント。
  • <インスタンス化されるクラスがコード中に直接書かれていることが最大の問題> (p. 93)

Abstract Factory

  • Motif とか Presentation Manager とかって何?

  • <各種の基本ウィジェットを生成するためのインタフェースを宣言した抽象クラス WidgetFactory を定義する> (p. 95)

  • 各種のウィジェットに対する抽象クラスを作成したら、 <その具象クラスで特定の look-and-feel 規格のもとでの実装を与える> (p. 95)

  • <たとえば Motif ではスクロールバーはボタンやテキストエディタとともに使わなければならないといった制約が、MotifWidgetFactory クラスを利用する結果として自動的に規定されることになる> (p. 96)

  • このパターンでは Client は AbstractFactory と AbstractProduct で宣言されたインタフェースのみを利用する (p. 97)

  • <普通、ConcreteFactory クラスのインスタンスは実行時に生成される> (p. 97) とあるが、実行時に生成されないインスタンスなど考えられる?

  • 新たな種類の部品への対応は AbstractFactory とそのすべてのサブクラスについて、インタフェースの修正が必要となる。これが面倒。

  • 部品を実際に生成するのは ConcreteProduct クラスになるが、各部品について factory method を定義する方法がよく用いられる (p. 98)

  • Prototype パターンを使って ConcreteFactory クラスを実装する方法がある。部品の集合が多数存在する場合にそうすることができる (p. 98)

  • <クラスをオブジェクトとして扱うことのできる言語では、 prototype を用いたアプローチに変化をつけることが可能になる> (p. 99)

  • 生成する部品の種類を表すパラメータを取る AbstractFactory の手法は、 <C++ を使うときには、すべてのオブジェクトが同じ抽象基底クラスを持つ場合か、要求を出すクライアントにより部品オブジェクトが正しい型に変換できる場合にのみ、適用することができる> (p. 100)

    この条件はそんなにきつくない。 <サブクラスに特有のオペレーション> (p. 100) をする必要がない場合は、この手法の採用の検討に値する。

サンプルコードで MazeGame::CreateMaze を Abstract Factory パターンで実装している。

  • <MazeFactory クラスは、単に factory method を集めたものになっているが、これは Abstract Factory パターンを実装するときにもっとも一般的な方法である> (p. 102)
  • AbstractFactory が ConcreteFactory を兼ねるのも一般的な実装方法 (p. 102)

Builder

  • 同じ作成過程で異なる表現形式の複合オブジェクトを生成できる (p. 105)

  • <変換すべきフォーマットのすべてを事前に確定できるとは限らないので、読み取り部を修正することなく、新たに与えられたフォーマットへの変換が容易に行えるようにしておくことが望ましい> (p. 105)

  • <RTFReader オブジェクトが RTF の文書の構文解析を行い、その結果を TextConverter オブジェクトを使って変換する> (p. 105)

    「何に」変換するかはまだ言及していないことに注意。

  • この各変換クラスを builder と呼び、読み取り部のクラスを director と呼ぶ (p. 106)

  • <RTFReader クラスの構文解析アルゴリズムは再利用することができる> (p. 106)

  • オブジェクトの作成プロセスが、多様な表現を認めるようにしておける (p. 106)

  • 当パターンのクラス構造を見ると、Director が Builder を持っている。面白いのは DirectorBuilder::BuildPart メソッドしか利用していないこと。

    • ConcreteBuilder は <Product オブジェクトを取り出すためのインターフェイスを提供する> (p. 107)
    • Product クラスは <多くの構成要素からなる複合オブジェクト> (p. 107) である。

このパターンはトレード・オフがないのか。

  • <別の Director オブジェクトが同じ構成要素からなる Product オブジェクトを作成する場合に、それを再利用することができるようになる> (p. 108)

    各 ConcreteBuilder がそのまま(変更せずに)再利用できると強調している。

  • <生成要求の結果を、それまでに得られている Product オブジェクトに単純に追加していくだけのモデルで十分な場合が多い> (p. 109)

  • <異なる Product オブジェクトに共通の親クラスを作るメリットは少ない> (p. 109)

    なるほど。

サンプルコードでは、MazeGame の例を Builder パターンを導入して書き直している。

  • MazeBuilder クラスにメソッド群 BuildXXXX を定義するメリットは、各 Product (Room, Door) の生成ロジックを隠蔽することにある。 <異なる種類の迷路を作成する場合に MazeBuilder クラスを再利用できることを意味している> (p. 110)
  • <MazeBuilder クラスは迷路そのものを作るのではなく、迷路作成のためのインターフェイスを定義しているにすぎない> (p. 110)
  • <しかし、Maze クラスを小さくしておくことで理解や修正が容易になるという利点があり、また、StandardMazeBuilder クラスは Maze クラスから容易に分離することもできる。もっと重要なことは、この 2 つを分離しておくことにより、部屋、壁、ドアに対して、異なるクラスを使ってさまざまな MazeBuilder クラスを作れるようになるという点である> (p. 112)
  • Abstract Factory パターンは <複合オブジェクトを作成するという点で Builder パターンに類似している> (p. 114)

Factory Method

フレームワーク寄りのデザインパターンらしい。

  • フレームワークにしばしば見られる特徴 (p. 115):

    • オブジェクト間の関係を表現するのに、抽象クラスを用いる。
    • (具象型のわからない)オブジェクトの生成を行う責任がある。
  • フレームワークの立場としては、<Application クラスは Document のどのサブクラスがインスタンス化されるのかを事前に知ることはできない> (p. 115) ので、Application クラスに Document を生成するオーバーライド可能なメソッド CreateDocument を用意し、ユーザーにサブクラス型を返すような実装をさせる。

    この CreateDocument みたいなものを一般に factory method と呼ぶ。

  • <クラス内部でオブジェクトを生成する場合、直接生成するよりも factory method を使うほうが柔軟性を高める> (p. 117)

  • 図形操作ツールの話 (pp. 118-119) が面白かったので、後でもう一回読んでみる。 Figure インタフェースに CreateManipulator (factory method) を与えておき、各 Figure のサブクラスがそれに応じた Manipulator のサブクラスを生成する、というトリック。

  • Creator クラスを抽象クラスにして、factory method を空にする場合と、 Creator クラスを具象クラスにして、factory method にデフォルト実装を与える場合がある (p. 118)

  • Factory Method パターンの変形として、<factory method が数種類の ConcreteProduct オブジェクトを生成できるようにしておく> (p. 119) ものがある。種類を表すパラメータを取るようなメソッドにするらしい。

    • この手法はシリアライズ実装で使うというようなことが書かれている。
  • 言語によっては <インスタンス化されるクラスを返すメソッド> (p. 120) を使う。オブジェクトではなく、クラス自体を返すということ。

  • C++ では Creator クラスのコンストラクタ内で factory method を呼び出せない (p. 121)

    そんなことをしたら実行時エラーが起こって即終了。

  • C++ ではさらに <テンプレートを用いてサブクラス化を避ける> (p. 121) 技法も駆使したい。

  • factory method には、見てそれとわかる名前を付けると便利 (p. 122)

サンプルコードでは MazeGame::CreateMaze を factory method で実装している。

  • <factory method は、ツールキットやフレームワークの中で広く採用されている> (p. 124)
  • Abstract Factory パターンは factory method を使って実装されることが多い (p. 125)

Prototype

既存インスタンスをコピーすることで新たなオブジェクトの生成を行うパターン。

  • 「動機」に書いてあること

    • Graphics: 音符、休止符、譜表、等々の図形オブジェクトを表現するための抽象クラス
    • Tool: ツールパレット上のツールを定義するための抽象クラス
    • GraphicTool: Graphics をドキュメントに追加するための Tool のサブクラス
    • <GraphicTool クラスは、音符などのクラスのインスタンスを楽譜に加えるためにどのように生成したらよいのかを知らない> (p. 127)
    • <Graphic のサブクラスのインスタンスをクローン化して、新たなオブジェクトを生成する方法> (p. 127) によって得られるインスタンスのことを prototype と呼ぶ。
  • <Prototype パターンは、Client オブジェクトに対してインスタンス化する具象クラスを隠蔽している> (p. 129)

  • <たとえば回路設計エディタでは、回路をいくつかの部分回路から作成するようになっている> (p. 130)

    つまり、部分回路が繰り返し使われる状況である可能性が高く、そうなれば当パターンの守備範囲だ。

  • <C++ のようにクラスが first-class オブジェクトとして扱われない言語> (p. 130) にとっては、Creator のクラス階層を作らずに済む当パターンにメリットがある。

  • <prototype マネージャ> (p. 130)

  • <その内部に複製をサポートしていないオブジェクトや循環する参照を持つオブジェクトを含む場合> (p. 131) 等、 prototype 各サブクラスで Clone を実装するのが困難な場合もある。

実装ポイント

  • Prototype パターンは C++ のような静的な言語において有効なパターンである (p. 131)

  • <Client オブジェクトは prototype を直接扱うのではなく、登録されている prototype オブジェクトを検索したり、新たに登録したりする> (p. 131)

    prototype マネージャは連想配列ベースのデータ構造。

  • <もっとも困難な点は、Clone オペレーションを正しく実装することである> (p. 131)

    • <複製を行うということを、元のインスタンス変数を共有させることにするのか、またはインスタンス変数の複製を行うことにするのか> (p. 131)
    • お手軽な Clone の実装例として、もしオブジェクトが Save/Load オペレーションを提供しているのであれば、これで実装できると言っている (p. 132)

サンプルコードのページでは MazeFactory の Prototype パターン版を紹介。

  • MazePrototypeFactory では <生成オブジェクトをあらかじめ prototype として持つように初期化> (p. 132) する。
    • MazePrototypeFactory::MakeXXXX では XXXX 型メンバーデータの prototype に対して Clone を呼び出し、戻り値をそのまま返す。
      • 場合によっては clone のパラメータを修正する。
    • <他の迷路を作成する場合には、MazePrototypeFactory オブジェクトを別の prototype で初期化すればよい> (p. 133)
  • <Client オブジェクトの側では、Clone オペレーションの返却値を望む型にダウンキャストしなくてもよいようにしておくべきである> (p. 135)

Singleton

ここは読まなくていいや。

まとめ

  • オブジェクトを生成するクラスをサブクラス化する方法
    • Factory Method パターンを使うことに対応。
  • 生成するオブジェクトのクラスを把握しているオブジェクトを定義してから、それをパラメータにする方法
    • Abstract Factory, Builder, Prototype パターンの基本。設計は柔軟だが、より複雑 (p. 146)
  • 図形エディタフレームワークを設計するのならば、 Factory Method パターンがもっとも使いやすいパターン (p. 145) だが、GraphicTool のサブクラスが多く必要になる。
  • <全般的に見て、Prototype パターンが図形エディタフレームワークにとって、おそらく最適なパターンになるだろう> (p. 146) Graphic::Clone のオーバーライドだけでよいから。
  • <Factory Method パターンを使うことで、設計はカスタマイズが容易になると同時に若干複雑になる> (p. 146)
  • 設計の初期段階では Factory Method パターンを採用しておき、様子を見て他のパターンに発展させていくやり方がよい (p. 146)

どの方法も複雑であると言っている?

第 4 章 構造に関するパターン

  • <クラスやオブジェクトを合成する方法に関係している> (p. 147) なるほど。構造イコール合成なのか。
  • 構造に関するパターンも、「オブジェクトに適用するもの」と「クラスに適用するもの」がある。前者が動的で後者が静的な性質のものだということなのだろう。

Adapter

このセクションは他のパターンのそれに比べて妙に長く感じた。

  • <再利用を目的として設計されたツールキットクラスは、そのインタフェースがアプリケーションの要求するドメインに特化したインタフェースと一致しないというだけの理由で、再利用できないことがある> (p. 149) もったいない話だ。

既存のツールキットクラス TextView をうまく再利用して、 LineShape や PolygonShape のテキスト版と言える TextShape というクラスを定義できないかを議論している。

  • <それに対して、テキストの表示と編集を行う TextShape クラスは、基本的なテキスト編集の歳にも、複雑な画面の更新やバッファの管理などをしなければならないため、実装はより困難であると考えられる> (p. 149)

  • <しかし TextView クラスを変更するのは勧められない。なぜならば、このツールキットが、ある 1 つのアプリケーションを動作させるためだけに、ドメインに特化したインタフェースを採用したとすると、このツールキット自体が汎用性を欠くものになってしまうからである> (p. 149)

  • ここでやりたいことは TextView を Shape に適合させること。

    • 方法 1: Shape のインタフェースと TextView の実装を継承したクラスを定義する。
    • 方法 2: TextView を持ったクラスを定義し、それは Shape インタフェースを有する。

    →クラスに適用する Adapter と、オブジェクトに適用するパターン (p. 152) があるということ。

  • <Shape のどのオブジェクトも、ユーザがインタラクティブにドラッグして別の場所に移すことができるようになっているべきである。ところが、TextView クラスは、それができるように設計されていない> (p. 150)

考慮すべき問題点を挙げている。

  • 何らかのインタフェースに一致させる作業が必要になるが、 <作業の範囲は、オペレーションの名前を変えるだけの簡単なインタフェースの変更から、まったく異なるオペレーションの集合をサポートすることまでが考えられる> (p. 152)
  • <インタフェースの適合機能が作りこまれているクラスを pluggable adapter と呼んでいる> (p. 153) の例として、 TreeDisplay を紹介している。
    • 異なる木構造は異なるインタフェースを持つことになるだろう。
    • 言い換えると、TreeDisplay ウィジェットはインタフェース適合機能を内部に組み込むべきなのである。

実装にも問題点が色々。

  • C++ の場合、クラスに適合するタイプの Adapter では、 Adaptee 側クラスを private 継承する。ということは、 Adapter クラスは Adaptee クラスのサブクラスではなくなる (p. 154)
  • 適合させなければならない最小限のオペレーションの集合を意識すること (p. 154)

サンプルコード。 <オブジェクトを基にした adapter の方が、コードの作成では若干の労力が必要になるが、より柔軟なものになっている> (p. 159) ポイントは、TextView のサブクラスでも OK だというところ。

関連パターン。 <アプリケーションにとっては、adapter よりも decorator の方が透過性が高い> (p. 161)

Bridge

最初に書いてある <抽出したクラスと実装を分離> の意味がわからない。

別名が Handle/Body とある。

動機ではクロスプラットフォームなウィンドウクラスライブラリの話を例に出している。

  • <さらに悪いことには、すべての種類のウィンドウに対して、 2 つずつ新たなクラスを定義していかなければならなくなるだろう> (p. 163)
  • <この Window クラスと WindowImp クラスの間の関係を bridge と呼ぶ> (p. 164)

適用可能性を見ると、クロスプラットフォーム以外にも使い途がある。特に C++ で威力を発揮するケースがあるようだ。

  • <クライアントのコードを再コンパイルしなくても済む> (p. 165)

  • <クラスの実装をクライアントから完全に隠蔽したい場合。 C++ では、クラスの内部表現はクラスのインタフェイスで見ることができてしまう> (p. 165)

    Exceptional C++ とかで議論していた Pimpl パターンの話を思い出す。

クラス構造を見ると一発で理解できる。

  • Implementor クラスが一種類しかない場合でも、クラスの実装上の変化がクライアントに影響を与えることがあってはならない場合には、 Abstraction/Implementor 分離は有効 (p. 167)

  • C++ の場合、Implementor の宣言を <私的なヘッダファイル> (p. 167) で行う。要するにクライアントが include できないファイルで宣言する。

  • Implementor の決定を他のオブジェクトに完全に委譲するという方法もある (p. 167)

    例えば Implementor の決定を専用の factory が行うことにすると、 Abstraction クラスと Implementor クラスの結合も間接的になる。

サンプルコードを検討すると、次のことに気付く。

  • Window のサブクラスのメソッドの実装は、すべて WindowImp のメソッドで実装している。

    // p. 170
    void Window::DrawRect(const Point& p1, const Point& p2){
        WindowImp* imp = GetWindowImp();
        imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y());
    }
    
  • WindowImp のサブクラスでのメソッド実装は、そのプラットフォームの API で実装している。例えば XWindowImp::DeviceRect は関数 XDrawRectanele で矩形を描画する、といった具合だ。

  • Window::GetWindowImp は Abstract Factory パターンでインスタンスを取得している。

Composite

  • <オブジェクトを木構造に組み立てる> (p. 175)
  • <個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができる> (p. 175)
  • <Composite パターンの特徴は、1 つの抽象クラスがプリミティブとコンテナの両方を表すことである> (p. 175)
  • プリミティブの意味がよくわからんが、 <プリミティブなオブジェクトは子を持たないため、子オブジェクトに関するオペレーションは実装しない> (p. 176)
  • <Draw オペレーションをその子オブジェクトの Draw オペレーションを呼び出すように実装し、またそれ以外にも、子オブジェクトに関連するオペレーションを実装する> (p. 176)
  • <Picture オブジェクトは別の Picture オブジェクトを再帰的に生成していくことができる> (p. 176)
  • Component クラスにおいて、親にあたる composite にアクセスするインタフェースを宣言するのはオプション (p. 177)
  • ある要求を composite が受け取ったとき、 <通常、その要求を子にあたる component に転送し、さらに転送の前後に付加的なオペレーションを実行することもある> (p. 177)

実装のセクションにある記述が濃い。

  • 親オブジェクトへの参照を持たせる場合、composite 構造の走査や管理が簡単になるが、「ある composite のすべての子オブジェクトは、その親オブジェクトとしてそれを持つ」という制約を壊さないように注意しないといけない (p. 178)
  • <Component クラスでサポートされているが、 Leaf クラスには無意味なオペレーションも多く存在する> (p. 179)
  • Add/Remove オペレーションをどのクラスで宣言するかは重要な問題。この議論に 2 ページ近くを割いている。普通は安全性を捨てて、透過性をとる方向に解決するのだろう。
  • 多くの設計では、<子オブジェクトの順番を明確にする> (p. 182)
  • <composite に、自身が削除されるときにその子オブジェクトの削除も一緒に行わせるようにするのが、通常ではもっとも良い> (p. 182) が、子オブジェクトが共有されているような場合は話は別だ。

Decorator

<サブクラス化よりも柔軟な機能拡張方法> (p. 187) を動的に行えるようだ。

  • <クラス全体に対してではなく、個々のオブジェクトに責任を追加したくなることがある> (p. 187)
  • いつぞやのスクロールバー付き枠付き TextView の例を持ちだしている。
    • <常にスクロールバーが必要とは限らない>
    • <必要になったときには ScrollDecorator オブジェクトを用いてスクロールバーを追加する> (p. 188)

Decorator クラスの構造は、

  • <component または decorator への参照を保持する>
  • <Component クラスのインタフェースと一致したインタフェースを定義する> (p. 189)

の二点。

  • <Decorator パターンを用いると、decorator を付けたりはずしたりして、実行時に簡単に責任の追加や削除ができる> (p. 190)

    個人的には削除の例は見たことがない。

  • <1 つの単純なクラスを定義し、decorator を用いて機能を段階的に追加していく> (p. 190)

  • Component クラスを軽く保つことが重要。メンバーデータは極力サブクラスに持たせる (p. 191)

  • <Component クラスが本質的に重く、そのため Decorator パターンを適用するにはコストがかかりすぎるような状況では Strategy パターンを選択する方がよい> (p. 191)

サンプルコード。コンストラクタの呼び出し方にインパクトあり。

// p. 194
window->SetContents(
    new BorderDecorator(
        new ScrollDecorator(textView, 1)
    )
);

使用例。

  • <ストリームはほとんどの I/O 機構に存在する基本的な抽象概念である> (p. 195)
  • decorator は adapter とは異なる。責任を変えるだけで、インタフェースまでは変えない (p. 196)
  • オブジェクトを変化させる方法には、decorator と strategy の 2 通りが考えられる (p. 196)

Facade

後回し。

Flyweight

このパターンは細かいオブジェクトの共有を目的とする。

  • <flyweight とは、複数の文脈で同時に利用され得る共有オブジェクトのことである> (p. 207)
    • 文脈とは何か。
    • 共有オブジェクトということは、状態の持ち方に特別な何かがありそうだ。
  • <ここでキーとなる概念は intrinsic 状態と extrinsic 状態の区別である> (p. 208)
    • intrinsic 状態は flyweight オブジェクトの内部に格納。
      • 文脈とは依存しない、独立した情報。
      • 共有できる情報。
    • extrinsic 状態は
      • 文脈に依存する情報。
      • 共有できない情報。
    • 文書エディタの例で言うと、
      • 各文字が flyweight オブジェクトであり、
      • 文字コードは intrinsic 状態。
      • 座標位置、フォントは extrinsic 状態。
  • <extrinsic 状態に依存する可能性のあるオペレーションは、 extrinsic 状態をパラメータとして渡される> (pp. 208-209)

適用可能性は、それを見極めるのがわかりやすいようだ。

  • 非常に多くのオブジェクトを利用する。

  • そのためにメモリ消費コストが高くつく。

  • <オブジェクトの状態を構成するほとんどの情報を extrinsic にできる> (p. 209)

  • あとはオブジェクトを共有できるかどうか。

  • <flyweight が機能するために必要な状態は、 intrinsic 状態か extrinsic 状態のどちらかに分類されなければならない> (p. 211)

  • flyweight オブジェクトは <FlyweightFactory オブジェクトから入手しなければならない> (p. 211)

    便宜上 Factory という単語を使っているだけであって、アクセスの度に常にオブジェクトを生成しているわけではない。

  • <格納コストをもっとも節約できるのは、オブジェクトが intrinsic 状態と extrinsic 状態の両方についてかなりの量の情報を持ち、しかも extrinsic 状態が格納されるのではなくて計算できる場合> (p. 212)

  • <オブジェクトは共有されるので、クライアントがそれらを直接インスタンス化すべきではない> (p. 212)

  • <オブジェクトの共有では、flyweight が不要になったときに(略)何らかの形の参照数管理やガーベッジコレクションが必要になる> (p. 213)

サンプルコードの見どころは GlyphContext クラス。一見しただけでは何を管理しているのか理解できない。

使用例

  • <flyweight の概念は、InterViews 3.0 における設計テクニックとして初めて記述され、研究された。その開発者は、この概念を立証するために、 Doc と呼ばれる強力な文書エディタを構築した> (p. 218)

    180000 文字を含む文書を 480 個の文字オブジェクトで賄えるケースがあったとか。

Proxy

<オブジェクトの代理、または入れ物> (p. 221)

  • <そのオブジェクトを実際に利用する必要が生じるまで、そのオブジェクトの生成と初期化にコストをかけるのを延期する> (p. 221)

  • <生成に高いコストのかかるオブジェクトをすべて同時に生成するのは避けることにする> (p. 221)

  • <文書の中には画像の代わりに何を置いておけばよいのだろうか> (p. 221)

  • <要求があり次第画像が生成されるという事実を隠蔽するにはどうしたらよいのだろうか> (p. 221)

    →画像 proxy なるものを導入することで解決する。

適用可能性として、4 種類の proxy を分類している。

  • remote proxy

  • virtual proxy

  • protection proxy: <実オブジェクトへのアクセスを制御する> (p. 223)

    オブジェクトごとに異なるアクセス権が必要な場合に有用らしい。

  • smart reference: <通常のポインタに代わるもの> (p. 223)

結果の説明でコピーオンライトについて言及がある。

  • <もしコピーされたオブジェクトが変更されないのであれば、このコストを発生させる必要はない。コピーするプロセスを延期するために proxy を使えば、そのオブジェクトが変更されたときにのみ、そのオブジェクトをコピーすればよいようにできる> (p. 225)

実装

  • <メンバアクセスオペレータをオーバーロードする方法は、どのような種類の proxy に対しても良い解決法になるとは限らない> (p. 226)
  • <Proxy クラスが RealSubject クラスをインスタンス化する場合(略)には、 Proxy クラスはその具象クラスを知っていなければならない> (p. 227)

まとめ

Composite, Decorator, Proxy の比較 (p. 234) が面白かった。

  • Decorator は退化した Composite ではない。
    • 両者は目的が異なっている。
    • ということは、相補的に利用できる。
  • Decorator も Proxy も、クライアントに合成前のインタフェースと同じものを与えるが、 Proxy に関しては
    • 特性を動的に加えたりはずしたりしない。
    • 再帰的な合成のために設計されていない。