Chapter 2 A CASE STUDY:DESIGNING A DOCUMENT EDITOR
ドキュメントエディタ「Lexi」を題材に、
- さまざまな設計上の困難と
- それを克服するために使用されているデザインパターン
を紐解いていく
2.2 Document Structure
Problem
-
LexiはWYSIWYGな感じの文書エディタ
- MS Wordみたいな感じか
-
編集対象の文書はさまざまな要素からなる
- テキスト
- 画像
- 基本図形
-
テキストを特別扱い、画像を特別扱い、とかはしたくない
- さもないとしくみが複雑になるから
Solution: Composition Pattern
- テキスト、画像、基本図形など、「目に見えるなにか」をGlyphとして汎化
-
目に見えない「行」「段組み」といったものもGlyph派生
- 「行」に相当するRowクラスはGlyphを集約する … 再帰的な木構造
2.3 Formatting
-
Formatting: 文書の成形方法
- どこで改行するか、とか
Problem
-
Formattingの頭の良さや美しさは実行速度とのトレードオフ
- WYSIWYGなので、実行速度も重要
-
Formattingのアルゴリズムには、以下のものがあるとする
- Array
- TeX
- Simple
- 段組みColumn : Glyphをまとめ、文書全体を表現するComposition : Glyphクラスがあるとする
-
各アルゴリズムに対応するCompositioin派生を作りたくなる:
- ArrayComposition
- TexCompositon
- SimpleComposition
-
Formattingは良し悪しなので、ユーザが実行時に選択できることが望ましい
- 上記の継承では実現できない
Solution: Strategy Pattern
-
アルゴリズムを汎化し、別の継承ツリーにはじき出す
- 文章をComposeする抽象クラス、Compositorを定義
-
アルゴリズム別の具象クラスを用意
- ArrayCompositor
- TeXCompositor
- SimpleCompositor
-
僕的には後置修飾のほうが好み
— 入力補完や、並んだ時の美しさの面で- CompositorArray
- CompositorTeX
- CompositorSimple
- Compositionクラスは、Formattingアルゴリズムに対応するCompositorインスタンス_compositorをもつ
-
Formattingアルゴリズムを選択するには、_compositorインスタンスを挿げ替える
- Compositor派生は状態をもつべきではない
- Compositionは文書をFormatする際、
_compositor.Compose()
に処理を委譲する
2.4 Embellishing the User Interface
文書に以下のものを加えたい
- 枠線
- スクロールバー
いずれも、文書そのものとは関係ない飾りつけである (embellishment)
Problem
- 飾りつけの処理の追加により本命の処理に影響を与えたくない
-
クラス爆発は勘弁
- BorderedComposition とか BorderdCompositionWithScrollBar とか悪夢
Solution: Decorator Pattern
- 本命の処理を行うクラス(Composition : Glyph)を、同じインタフェース(Glyph)で包んでやり、処理を追加する
- 同じインタフェースGlyphで包むので、Glyph利用側からは包む前後で区別がつかない (Transparent Enclosure)
-
いま、Glyphをちょうど1つ集約するGlyph派生、Monoglyphを定義する
枠線追加クラスBorder、スクロールバー追加クラスScrollerの定義は下記のようになる-
Monoglyph : Glyph
- Border : Monoglyph
- Scroller : Monoglyph
-
- 下記のように集約させると、文書にスクロールバーがつき、その周りに枠線がつく
Border <>-- Scroller <>-- Composition <>-- ...
- もしも、枠線ごとスクロールしたければこう。クラス爆発も回避できる
Scroller <>-- Border <>-- Composition <>-- ...
- Composition以降の集約関係や、GlyphとしてCompositionを利用していたコードには変更が一切生じない
2.5 Multiple Look-and-Feel Standards
Problem
- プラットフォームによって、ボタンやスクロールバーなど(ウィジェットと総称)の外観と雰囲気が異なる
- ユーザーを混乱させないためには、プラットフォーム標準に合わせたい
- すぐ思いつくのはこのような実装
各ウィジェットの各プラットフォーム版クラスを作り、利用側でnewする
ScrollBar* sb = new MacScrollbar();
Button* btn = new MacButton();
...
-
良くない点
- 他プラットフォームへの移植が大変
- 不注意で
WindowsButton()
とかが混ざる可能性がある
Solution: Abstarct Factory
- ScrollBarとかButtonとか、ウィジェット一式を作るファクトリクラスGUIFactory をつくる
-
プラットフォーム別に具象クラスをつくる
- GUIFactoryMac : GUIFactory
- GuiFactoryWindows : GUIFactory
- ウィジェット生成コードはこうなる
GUIFactory* guiFactory = new GUIFactoryMac();
ScrollBar* sb = guiFactory->CreateScrollBar();
Button* btn = guiFactory->CreateButton();
...
- Windows版にしたくなったらこう
- GUIFactory* guiFactory = new GUIFactoryMac();
+ GUIFactory* guiFactory = new GUIFactoryWindows();
ScrollBar* sb = guiFactory->CreateScrollBar();
Button* btn = guiFactory->CreateButton();
...
- GUIFactory*は、プラットフォーム判明後、ウィジェット生成ならどこで設定してもいい(実行時に選択できる)
2.6 Supporting Multiple Window Systems
Problem
-
ウィンドウはプラットフォームにより仕様が大きく異なる -グラフィック描画
- 最小化・元に戻す
- 座標系
-
各プラットフォームのウィンドウに対して、下記のバリエーションがある
- アプリケーションのメインのウィンドウ
- ダイアログ
-
プラットフォーム別ウィンドウの派生を作るのは良くない
- サブツリーをビルド時に切り替えないといけない
-
フラットにするのはもっとよくない(AppMacWindowとか)
- クラス爆発
Solution: Bridge Pattern
-
プラットフォーム別実装を別の継承ツリーに切り出す
- WindowImp抽象クラスとその派生
- 具象クラスをインスタンシエートするには、AbstractFactoryをつかう
- Windowの継承ツリーは、あくまで「メイン」「ダイアログ」といった「種類」に使う