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

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

ローマ数字ページ

  • 監訳者曰く <現在、確固たる地位を築いているのは UML と、本書に掲載されている GoF パターンだけであると言っても過言ではない> (p. iii)
  • <実際は、すべて一人の人物によって書かれた章はほとんどない> (p. iv)
  • 本書の対象読者の想定レベルが <我々が型やポリモルフィズムについて(略)論じたからと言って、ただしに近くの解説書に手を伸ばすようなことではだめである> (p. v) と厳しい。
  • デザインパターンは <標準的なオブジェクト指向言語を用いて実装できるものばかりである> (p. v)
  • <実のところ我々も、最初に書いたときにすべてを理解していたわけではない。本書は、一回読んでその後本棚にしまっておくような本ではない> (p. v)
  • あるメールアドレスにあるメッセージを送ると、本書の「サンプルコード」のコードを入手できる。
  • <もっともシンプルで、かつ、もっとも使われている以下のパターンから読み始めるとよいだろう> (p. viii) とあるので、素直に従ってみる。
    • Abstract Factory パターン
    • Factory Method パターン
    • Adapter パターン
    • Composite パターン
    • Decorator パターン
    • Observer パターン
    • Strategy パターン
    • Template Method パターン

第 1 章 概論

  • <初心者が「良いオブジェクト指向設計とは何か」を学ぶには多大な時間がかかる> (p. 13)
  • <良い解法を見つけたときには、それを何回も何回も用いる> (p. 13)
  • <小説家や脚本家が構想をゼロから創作することはめったにない> (p. 13)
  • <本書の目的は、オブジェクト指向ソフトウェアを設計する際の経験を デザインパターン として記録することである> (p. 14)

1.1 デザインパターンとは

  • パターンは次の 4 つの基本的な要素を有している
    • パターン名称
    • 問題:<どのような場合にパターンを適用すべきか> (p. 15)
    • 解法:<パターンはさまざまな状況に適用できるテンプレートのようなもの> (p. 15)
    • 結果:結果と共に、適用時のトレードオフも記述する。
  • <何がパターンで何がパターンでないかの解釈は、人それぞれである> (p. 15) セオリーは人によるのさ by 安永五段か。
  • 本書で扱うデザインパターンを <設計上の一般的な問題の解決に適用できるよう、オブジェクトやクラス間の通信を記述したもの> (p. 15) と宣言している。
  • これらのパターンの中には <あまり一般的でない言語によって直接サポートされている> (p. 16) ものがあるらしい。

1.2 Smalltalk MVC

Smalltalk とはなんぞや。後で調べる気になったら調べればいいか。

  • M: Model, V: View, C: Controller
  • MVC におけるデザインパターンを見れば、パターンの意味するところを理解できる (p. 16)
  • MVC では柔軟性と再利用性を向上させるべく、これらを独立して扱う (p. 16)
  • View オブジェクトは Model オブジェクトの状態を反映することを保証 (p. 16)
    • View と Model を分離するという設計例を、より一般的な問題にも適用できる。これは Observer パターンの特殊な場合とみなせる。
  • View オブジェクトをネストできる。 View のサブクラスとして CompositeView クラスを定義し、そこにネストする (p. 17)
    • この設計例もより一般的な問題に適用できる。オブジェクトのグループ化だ (p. 17) → Composite パターンの例だろう。
  • <Controller オブジェクトにはクラス階層が存在し、これを利用することにより、既存の Controller クラスの派生として簡単に新しい Controller クラスを生成することができる> (p. 17) → Strategy パターンの一例。

1.3 デザインパターンの記述

<設計を再利用するためには、設計プロセスの途中でのさまざまな決定事項や代替案、トレードオフも記録しておかねばならない。(略)具体的な例も必要である> (p. 18)

パターンの記述法をパターン化することで、カタログたり得るということか。

1.5 カタログの構成

要注意だと思ったのは、デザインパターンを「クラスパターン」と「オブジェクトパターン」に分けていること。パターン数は前者が圧倒的に少ない。

  • クラスパターンはクラスとサブクラス間の関連を扱い、静的なもの。
  • オブジェクトパターンはオブジェクト間の関連を扱い、動的なもの。
  • パターンの中にはしばしば他のパターンと一緒に用いるものがある。 Composite パターンは Iterator パターンや Visitor パターンと共に用いる (p. 24)

1.6 デザインパターンで設計問題を解く

このセクションは妙に長い。

  • <設計過程に置いて生じる抽象的な考え方は、設計を柔軟にしてくれる> (p. 25)
  • <インタフェースはオブジェクト指向システムの基本である> (p. 26)
  • <デザインパターンはインタフェースとして記述してはいけない事項も教えてくれる> (p. 26) として、Memento パターンがその例であることを説明している。
  • <たいていのプログラミング言語ではインタフェースの継承と実装の継承の間を区別していないが、実際にはプログラマはこれらを区別して扱っている> (p. 29)
  • サブクラス化による再利用はホワイトボックス再利用 (p. 30)
  • オブジェクトコンポジションによる形式の再利用はブラックボックス再利用 (p. 31)
  • <継承にもコンポジションにも、それぞれ利点と欠点がある> (p. 31)
  • <理想的には、再利用を実現するために、新たに部品を作り出す必要がないようにしておくべきである> (p. 31)
  • <委譲の主な利点は、実行時に動作を合成することが容易であること、合成する方法の変更が容易であることである> (p. 32)
  • <委譲にも欠点がある。(略)静的なソフトウェアよりも理解しにくい> (p. 32)
  • 集約関係の説明: <集約オブジェクトがその保有者とは同一のライフタイムを有することを意味する> (p. 34)
  • 集約関係 (aggregation) と知り合い関係 (acquaintance) は、しばしば同じ方法で実装されるので混同しやすい (p. 34)
  • <再設計を余儀なくされるいくつかの原因> (p. 35) と回避策のリスト (p. 36) がためになる。
    • 特定の実装に委ねるのを避け、間接的にオブジェクトを生成する。
    • プラットフォームへの依存度をできるだけ小さくするように設計する
    • クライアントに対して、実装の詳細を隠す。
    • 変更する可能性のあるアルゴリズムは局所化する
    • 結合度の低いシステムの実現を支援するために、抽象化あるいは階層化技法を用いる。
    • オブジェクトコンポジションを多用すると、理解しにくくなるのも事実 (p. 37)

1.8 どのようにデザインパターンを使うか

<デザインパターンを無秩序に適用すべきではない。(略)そのデザインパターンの与える柔軟性が真に必要な場合にのみ適用すべきである> (p. 42)

第 2 章 事例:ドキュメントエディタの設計

架空のドキュメントエディタ Lexi の設計事例を通じて 8 つのデザインパターンを読者に習得させる章らしい。

2.2 ドキュメント構造

  • ドキュメントは結局、グラフィックの基本的な要素を並べたものにすぎない (p. 45)
  • グラフィックをテキストの特別なケースとして(またはその逆も)扱うことは避けたい (p. 46)
  • 階層構造の情報を表現するためには、再帰構成という技術を用いるのが一般的 (p. 46)

2.3 整形

  • <Lexi は WYSIWYG エディタであるから、整形の質と実行スピードの間のバランスという点が、考慮すべき重要なトレードオフの 1 つである> (p. 50)
  • 整形アルゴリズムについて <もっと言えば、完全にドキュメント構造と独立であることが強く望まれる。理想的には、整形アルゴリズムに関係なく Glyph クラスの新しいサブクラスを加えられるとよい。また逆に、新しい整形アルゴリズムを加える場合に、既存の glyph を変更する必要があってはならない> (p. 51) という、かなり強烈な要求がある。
  • <新しいアルゴリズムをサポートするために戦略や対象のインタフェースを変更しなければならない、ということがないようにしなければならない> (p. 53)

2.4 ユーザインタフェースの装飾

  • スクロールバーと境界線の両方が欲しいからと言って、 BorderedScrollableComposition のようなサブクラスを作っていては、クラスの爆発という深刻な問題が起こる (p. 53)
  • 透明な囲い (p. 54)
  • Glyph のサブクラス MonoGlyph を定義し、Glyph オブジェクトを参照させる。そして MonoGlyph のサブクラスとして Scroller と Border をそれぞれ定義する。
    • Scroller と Border の構成順序はどちらが先でも可 (p. 55)
  • <Decorator パターンでは、オブジェクトに責任を追加するものであれば何でも装飾と捉える> (p. 56)

2.5 さまざまな look-and-feel 規格をサポートする

個人的にはこれが一番興味のない要求なのだが、技法的な説明は面白い。

  • 実行時に look-and-feel 規格を変更できたら、究極の自由度の実現である (p. 57)
  • <コンストラクタコールを使ってしまうと、このようなことを直接行えない> (p. 57) そこで、オブジェクト生成プロセスの抽象化という発想が生まれる。
ScrollBar* sb = new MotifScrollBar;

ではなく、

ScrollBar* sb = guiFactory->CreateScrollBar();

のように、<Motif という名前に言及するようなコードがなくなっている> (p. 58) ような工夫が必要。

  • オブジェクト guiFactory はどこから得るのかというと、<どこでもよい> (p. 59) らしい。
  • <重要なことは、正しい Factory オブジェクトによって一度アプリケーションを形成すれば、その時点から look-and-feel 規格はセットされているということである> (p. 60)

2.6 さまざまなウィンドウシステムをサポートする

  • 前節とは一転して <ウィンドウシステム間の移植性の制約は、 look-and-feel 規格に対する場合とはかなり異なる> (p. 61)
  • 前節は Abstract Factory パターンで、本節は Bridge パターンの説明になる。

2.7 ユーザのオペレーション

要求のカプセル化→Command パターン。

  • 特定のオペレーションを特定の UI と関連付けるようなことはしない。そうではなく、同じオペレーションに対して複数の UI を規定したい (p. 68)
  • オペレーションについて、undo/redo をサポートさせたい。ただし、 <描いたものを保存したり、あるいは、アプリケーションを終了したりするオペレーションに対しては取り消しをすべきではない> (p. 69)

2.8 スペルチェックとハイフン挿入

  • <この機能をドキュメント構造の中に組み込んでしまうことは避けたい> (p. 73)
  • <抽象クラス Glyph には、子を保持するためのデータ構造をカプセル化するという重要な役割がある> (p. 74)
  • <走査する過程のメカニズムを完全に Glyph クラス階層の中に入れてしまうと、変更や拡張においてどうしても多くのクラスに変更を加えることになる> (p. 75)

Iterator パターンの話になってくる。

  • 繰り返しの問題は意外に深い (p. 78)
  • スペルチェックとハイフン挿入は異なる解析であるが、走査方法自体は同じになる。 <したがって解析と走査方法とは分離すべきである> (p. 79)
  • <これまでに何度も行ってきたように、解析をオブジェクトと分離してカプセル化する> (p. 79)
  • <適切な Iterator オブジェクトと一緒にこのクラスのインスタンスを使えばよい> (p. 79) 図によると深さ優先探索をするらしい。

Visitor パターンの話になってくる。

// p. 83
class Visitor{
public:
    virtual void VisitCharacter(Character*){}
    virtual void VisitRow(Row*){}
    virtual void VisitImage(Image*){}

    // ...
};
  • SpellingCheckVisitor, HyphenationVisitor はこのサブクラスとして定義する。
  • Glyph::CheckMe はより一般的な Accept という名前にする (p. 84)
  • Visitor パターンは <安定した構造を持つオブジェクトに対して多様な働きを実現したいようなときに一番適合する> (p. 85)
  • 構造にサブクラスを追加するときはいつでも、そのサブクラス用の Visit... オペレーションを追加しなければならない (p. 85)

2.9 まとめ

本章で見てきたパターンの多くは、他の分野のアプリケーションの設計でも利用する機会があるだろう (p. 85)