勉強日記

チラ裏

GoF本 Chapter 2 WIP

Chapter 2 A CASE STUDY:DESIGNING A DOCUMENT EDITOR

ドキュメントエディタ「Lexi」を題材に、

  1. さまざまな設計上の困難と
  2. それを克服するために使用されているデザインパターン

を紐解いていく

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

  • ウィンドウはプラットフォームにより仕様が大きく異なる -グラフィック描画
    • 最小化・元に戻す
    • 座標系
  • 各プラットフォームのウィンドウに対して、下記のバリエーションがある
    • アプリケーションのメインのウィンドウ
    • ダイアログ
  • プラットフォーム別ウィンドウの派生を作るのは良くない
    • サブツリーをビルド時に切り替えないといけない

f:id:wand_ta:20181130235519p:plain
antipattern: プラットフォーム別ウィンドウを派生

  • フラットにするのはもっとよくない(AppMacWindowとか)
    • クラス爆発

Solution: Bridge Pattern

  • プラットフォーム別実装を別の継承ツリーに切り出す
    • WindowImp抽象クラスとその派生
    • 具象クラスをインスタンシエートするには、AbstractFactoryをつかう
  • Windowの継承ツリーは、あくまで「メイン」「ダイアログ」といった「種類」に使う