MixJuice によるデザインパターン改善カタログ (α版)

[Single page version] [英語版]

デザインパターンはソフトウェアの拡張性・再利用性を高める構成法のカタログである。しかし、拡張性・再利用性を指向しているのはデザインパターンだけではなく、言語自身も拡張性・再利用性を意図して設計されることがあり、そのような言語で既存のデザインパターンを使うと、拡張性・再利用性にさまざまな(往々にして良い)影響が現れる。ここでは、Java をベースとして拡張性・再利用性を指向した差分ベースモジュールという機構を持つ言語である MixJuice について、そのデザインパターン(GoF パターン 23種)への影響を述べる。

MixJuice のデザインパターンへの影響

従来のオブジェクト指向言語で各パターンを使用する際に起きる問題点のうち、 MixJuice を用いることで改善できるものを表にまとめた。なお、 MixJuice による改善策の中にはトレードオフがあるものもある。 (表内のページ数は MixJuice が改善する問題点が、「デザインパターン」日本語版のどのページで述べられているかを示すものである。詳細については各パターンの説明のページを参照。)

MixJuice のデザインパターンへの影響
デザインパターン種別導入拡張情報隠蔽型安全性単純化使用するプログラミング技法
AbstractFactory改善p.98アブストラクトメソッド追加
別解p.98p.100実装モジュール選択
Builder改善p109メソッド追加メソッド拡張
別解メソッド拡張実装モジュール選択
FactoryMethod改善p.118名前空間分離
別解実装モジュール選択
Prototype改善p.131アブストラクトメソッド追加
Singleton別解p.138クラスメソッド拡張
Adapter別解p.153p.152スーパーインターフェース追加
Bridge改善アブストラクトメソッド追加メソッド追加
別解実装モジュール選択
Composite改善スーパーインターフェース追加
Decorator改善p.191メソッド追加アブストラクトメソッド追加
別解p.190クラス拡張
Facade改善p.201名前空間分離
別解クラスメソッド追加
Flyweightなし
Proxyなし
ChainOfResponsibility改善p.241スーパーインターフェース追加アブストラクトメソッド追加
Commandなし
Interpreter改善p.265アブストラクトメソッド追加
Iterator改善p.280名前空間分離
Mediatorなし
Memento改善p.307名前空間分離
Observer改善p.318クラス拡張
State改善アブストラクトメソッド追加
Strategy改善p.339アブストラクトメソッド追加
別解p.340実装モジュール選択
TemplateMethod改善p.351メソッド実装
Visitor改善1p.359仕様モジュールと実装モジュールの分離
改善2p.358アブストラクトメソッド追加
別解アブストラクトメソッド追加

また、個々のパターンに関する利点とは別に、ひとつのクラスが複数のパターンに参加する場合 MixJuice では各パターンを分離して情報隠蔽を行なうことが容易という利点がある。なお、パターンはコラボレーションをなすことが多く、その場合、コラボレーションを分離できることを意味する。

引用について

本カタログは Design Patterns (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Addison-Wesley, ISBN0-201-63361-2) およびその日本語版の「デザインパターン」(本位田真一、吉田和樹監訳、ソフトバンク株式会社、ISBN4-89052-797-4)からの引用をかなり含んでいる。各パターンの目的と、デザインパターンの問題点を述べている部分についてはパラグラフ単位でそのまま引用した。また、各パターンの構造を表すクラス図は、最新の UML の記法に描き直して用いた。

種別について

このカタログに載せたパターンの改善策のうちのいくつかは、 GoF パターンのクラス構造とは異なるクラス構造を使用しており、ここではそのようなものを別解と呼んでいる。

改善点の分類

MixJuice により、デザインパターンにはさまざまな影響が現れるが、ここでは影響を次の 5種に分類する。

導入可能性

既存のクラスをあるデザインパターンの新たな構成要素にするために、従来はソースコードの編集の必要があったものが、 MixJuice を用いることによって、編集の必要がなくなる。逆に言えば、1つのクラスが複数のデザインパターンに参加している場合でも、個々のデザインパターンごとに別のモジュールに分離して記述可能である。つまり、1つのデザインパターンに関するアスペクトを分離することが可能である。このことはプログラムのモジュラリティ向上に貢献する。

拡張性

パターンを用いたプログラムに新たな機能を追加するとき、従来はソースコードの編集の必要があったものが、 MixJuice を用いることによって、編集の必要がなくなる。このことは、パターンを用いたプログラムを機能単位で異なるモジュールに分離できることを意味する。

情報隠蔽

クラス単位の情報隠蔽機構しかない場合に比べて、 MixJuice を用いることによって情報隠蔽の精度が向上する。「情報隠蔽の精度が向上」とは2つのことを意味する。1つはある名前を参照することができるスコープのサイズが減少するということ、もう1つは、ソースコード中のある地点から参照可能な名前の数が減少する、ということである。前者は、ある名前の定義を変更した時に、その変更が影響を及ぼし得る範囲を小さくすることを意味する。後者は、名前の衝突の可能性を減らすことを意味し、それにより簡潔で分かりやすい名前を用いやすくなる。 Java のように package や nested class の機構があれば、 MixJuice でなくても改善できる項目も、このカタログに載せている。

型安全性

ダウンキャストの必要があったものが、不要になる。これは、 GoF パターンではない「別解」によって可能になる。

クラスモデルの単純化

クラスの数が減少し、クラスモデルが単純化する。これは、 GoF パターンではない「別解」によって可能になる。これにより、プログラムの実行時のイメージを理解やすくなる。1つのクラスの定義が複数のモジュールに分割される場合があるので、必ずしもソースコードが単純化するとは限らない。

MixJuice プログラミングの技法

デザインパターンの改善策において使用される、 MixJuice のプログラミング技法には次のようなものがある。

ここで、MixJuice 以外にも同様な技法を使える言語がある。たとえば、CLOS ではメソッド追加が可能である。そのような言語では MixJuice と同様なデザインパターンの改善が行なえることがある。

言語と技法
技法AspectJCLOSRuby
メソッド拡張around advicealias/def
スーパーインターフェース追加declare parentsdefmethodinclude
フィールド追加introduction代入
メソッド追加defmethodclass が実行文
アブストラクトメソッド追加不要/メソッド追加と同様
名前空間分離aspectpackage
仕様モジュールと実装モジュールの分離
実装モジュール選択aspect選択require 切替え

連絡先

不適切な点などがありましたら、mj-language ML または田中哲 <akr@m17n.org> まで連絡して下さい。

Abstract Factory

パターンの目的

p.95, line.3-4: 互いに関連したり依存し合うオブジェクト群を、その具象クラスを明確にせずに生成するためのインタフェースを提供する。

GoF パターンのクラス図

structure-08-05:abstractfactory

MixJuice 版 Abstract Factory (改善)

解決される GoF パターンの問題点

拡張性の問題点

p.98, line.10-15: 4.新たな種類の部品に対応することが困難である。新たな種類の部品に対応できるようにAbstractFactoryパターンを拡張することは容易ではない。なぜならば、AbstractFactoryクラスのインターフェイスは生成される部品の集合を固定しているからである。新たな種類の部品への対応は、インターフェイスの修正が必要となるために、AbstractFactoryクラスだけでなくそのすべてのサブクラスを修正しなければならなくなる。[実装]の節で、この問題に対する1つの解決策について議論する。

補足:最後に触れられている「1つの解決策」とは、実装の節の3項目めに書いてある。オブジェクトを生成するオペレーションに生成すべきクラスを指定するパラメータを付加するというもの。この方法を使うと返値のダウンキャストが必要になる。

対策

AbstractFactory クラスに対するアブストラクトメソッド追加で対処可能である。

結果

既存のソースコードを修正しなくても、新たな種類の部品の生成に対応することが可能になる。しかし、既存のすべての ConcreteFactory に対し追加された method を実装する補完モジュールが必要となる。

構造

structure-08-09:abstractfactory1

module original {
  define abstract class AbstractFactory {
    define abstract AbstractProductA createProductA();
    define abstract AbstractProductB createProductB();
  }
  define class ConcreteFactory1 extends AbstractFactory {
    AbstractProductA createProductA(){...}
    AbstractProductB createProductB(){...}
  }
  define class ConcreteFactory2 extends AbstractFactory {
    AbstractProductA createProductA(){...}
    AbstractProductB createProductB(){...}
  }

  define abstract class AbstractProductA {...}
  define class ProductA1 extends AbstractProductA {...}
  define class ProductA2 extends AbstractProductA {...}

  define abstract class AbstractProductB {...}
  define class ProductB1 extends AbstractProductB {...}
  define class ProductB2 extends AbstractProductB {...}

  define class Client {
    AbstractFactory factory;
    define void m1(){
      AbstractProductA a = factory.createProductA();
      AbstractProductB b = factory.createProductB();
    }
  }
}
module extension extends original {
  class AbstractFactory {
    define abstract AbstractProductC createProductC();
  }
  class ConcreteFactory1 {
    AbstractProductC createProductC(){...}
  }
  class ConcreteFactory2 {
    AbstractProductC createProductC(){...}
  }

  define abstract class AbstractProductC {...}
  define class ProductC1 extends AbstractProductC {...}
  define class ProductC2 extends AbstractProductC {...}

  class Client {
    define void m2(){
      AbstractProductC c = factory.createProductC();
    }
  }
}

MixJuice 版 Abstract Factory (別解)

解決される GoF パターンの問題点

拡張性の問題点

p.98 「4. 新たな種類の部品に対応することが困難である。」

新たな種類の部品に対応するためには、AbstractFactory クラスの修正が必要になる。

この問題に対処するために、引数で種類を指定するという方法が述べられているが、この方法は静的型にそぐわないという問題がある。

型安全性の問題点

p.100, line.12-18: すなわち、すべての部品が、戻り値の型により与えられる同一の抽象化されたインタフェースを備えた形でクライアントに返される。したがって、クライアントは部品のクラスに関して区別をしたり、安全な仮定をおくことが不可能になる。もし、クライアントがサブクラスに特有のオペレーションを実行したくても、抽象クラスのインタフェースを通してでは、それらを実行することはできない。クライアントはダウンキャストを(たとえば C++ の dynamic_cast を使って)行なうことができるかも知れないが、これは常に実行可能、または安全であるとは限らない。なぜならば、ダウンキャストは失敗する可能性があるからである。

生成された対象の内容をアクセスするためにダウンキャストが必要になりがちである。 GoF のウインドウシステムの例でいえば、ウインドウにピックスマップを背景として張り付ける場合、ウインドウオブジェクトはピックスマップオブジェクトのシステム依存な情報を得るためにダウンキャストを必要とする。

クラス数増加の問題点
生成される対象(Product)に加え、生成時だけに使われるクラス(Factory)が必要になる。

対策

実装モジュール選択を使う。

結果

MixJuice では concrete product と abstract product をまとめてひとつのクラスにしても、生成するオブジェクト群を選択可能にするというパターンの目的を達成することができる。これは、差分によってひとつのクラスのインターフェースと実装を異なるモジュールに記述することができ、リンク時に実装を選択することができるためである。

ここで述べた対策が適用可能なのはひとつのバイナリがひとつの concrete factory しか必要としない場合である。このような場合は GoF 本に述べられているように典型的である。

p.98 「典型的なアプリケーションでは、部品の集合ごとに ConcreteFactory クラスのインスタンスを 1つしか必要としない」

p.90, line.12-13, 1.Factories as singletons

An application typically needs only one instance of a ConcreteFactory per product family.

構造

structure-08-09:abstractfactory2

module products {
  define class ProductA {
    define abstract ProductA();
    ...
  }
  define class ProductB {
    define abstract ProductB();
    ...
  }
}

module products.implementation1 extends products {
  class ProductA {
    ProductA() {...}
    ...
  }
  class ProductB {
    ProductB() {...}
    ...
  }
}

module products.implementation2 extends products {
  class ProductA {
    ProductA() {...}
    ...
  }
  class ProductB {
    ProductB() {...}
    ...
  }
}

module client extends products {
  ... new ProductA() ...
  ... new ProductB() ...
}

サンプルコード

structure-08-09:abstractfactory_sample

module window_system {
  define class Window {
    define abstract Window();
    ...
  }
  define class ScrollBar {
    define abstract ScrollBar();
    ...
  }
}

module motif extends window_system {
  class Window {
    Window() {...}
    ...
  }
  class ScrollBar {
    ScrollBar() {...}
    ...
  }
}

module presentation_manager extends window_system {
  class Window {
    Window() {...}
    ...
  }
  class ScrollBar {
    ScrollBar() {...}
    ...
  }
}

module client extends window_system {
  ... new Window() ...
  ... new ScrollBar() ...
}

Builder

パターンの目的

p.105,line.3-4: 複合オブジェクトについて、その作成過程を表現形式に依存しないものにすることにより、同じ作成過程で異なる表現形式のオブジェクトを生成できるようにする。

GoF パターンのクラス図

structure-08-05:builder

MixJuice 版 Builder (改善)

解決される GoF パターンの問題点

拡張性の問題点

p.108,line.35 - p.109,line1-3: 1.生成と組み立てのインターフェイス。ConcreteBuilderクラスは、Product オブジェクトを段階的に作成する。ゆえに、Builderクラスのインターフェイスは、あらゆる種類のConcreteBuilderクラスでのProductオブジェクトの作成に適応できるように、十分に一般的でなければならない。

補足:設計段階で予期していなかった種類の Product オブジェクトの作成が必要になった時、 Builder クラスが提供されているインタフェースでは不十分な場合がある。

対策

Builder クラスに新たなメソッド(例えば buildPartC )を追加し、 Director から呼べるようにする。このとき、追加したメソッドを abstract method にする方法と、中身が空のメソッドにする方法がある。前者の場合、既存のすべての ConcreteBuilder に buildPartC の実装を追加するための補完モジュールが必要になる。

結果

Builder クラスであらかじめ定義した生成のためのインターフェースが不十分であった場合にでも、あとからインターフェースを拡張できる。

構造

structure-08-09:builder1

module original {
 define abstract class Builder {
   define void buildPartA(){}
   define void buildPartB(){}
 }
 define class ConcreteBuilder extends Builder {
   void buildPartA(){...}
   void buildPartB(){...}
 }
 define class Director {
   Builder builder;
   define void construct(){
     builder.buildPartA();
     builder.buildPartB();
   }
 }
}
module extension extends original {
 class Builder {
   define void buildPartC(){}
 }
 define class ConcreteBuilder2 extends Builder {
   void buildPartA(){...}
   void buildPartB(){...}
   void buildPartC(){...}
 }
 class Director {
   void construct(){
     original();
     builder.buildPartC();
   }
 }
}

サンプルコード

コメントを無視するパーザから、コメントノードを生成するパーザへの拡張。

MixJuice 版 Builder (別解)

解決される GoF パターンの問題点

クラス数増加の問題点

対策

GoF 本では RTF からさまざまな形式への変換する TextConverter が例として述べられている。この例では、 RTF の読み込み部を共通とし、書き出し部を可変にするために Builder パターンが使われている。

TextConverter の例において、各書き出し形式毎に rtf2ascii, rtf2tex, rtfview といった複数のバイナリを生成する場合には、個々のコマンド毎に静的に書き出し部が決定する。この場合、MixJuice では書き出し部分を RTFReader への差分として各種の実装を行なえ、出力部分をクラスとする必要がなくなり、クラス階層の単純化が可能である。

結果

MixJuice では ConcreteBuilder, Builder を Director をまとめてひとつのクラスにしても、同じ生成過程で異なる表現形式のオブジェクトを生成できるようにするというパターンの目的は達成できる。

なお、ひとつのバイナリ内で出力形式を切替えたい時には複数の書き出し部をバイナリ内に共存させなければならないため、このように差分を使用することはできない。

構造

structure-08-09:builder2

module builder {
  define class Builder {
    // Director の機能も持っている
    define void construct() { ... }

    define void buildPartA() {}
    define void buildPartB() {}
    define void buildPartC() {}
  }
}

module builder.implementation1 extends builder {
  class Builder {
    void buildPartA() { ... }
    void buildPartB() { ... }
    void buildPartC() { ... }
  }
}

module builder.implementation2 extends builder {
  class Builder {
    void buildPartA() { ... }
    void buildPartB() { ... }
    void buildPartC() { ... }
  }
}

サンプルコード

structure-08-09:builder_sample

module rtf {
  define class RTFReader {
    define void parseRTF() { ... }

    define void convertCharacter(char c) {}
    define void convertFontChange(Font f) {}
    define void convertParagraph() {}
  }
}

module rtf.ascii extends rtf {
  class RTFReader {
    void convertCharacter(char c) { ... }
    define ASCIIText getASCIIText() { ... }
  }
}

module rtf.tex extends rtf {
  class RTFReader {
    void convertCharacter(char c) { ... }
    void convertFontChange(Font f) { ... }
    void convertParagraph() { ... }
    define TeXText getTeXText() { ... }
  }
}

module rtf.textwidget extends rtf {
  class RTFReader {
    void convertCharacter(char c) { ... }
    void convertFontChange(Font f) { ... }
    void convertParagraph() { ... }
    define TextWidget getTextWidget() { ... }
  }
}

Factory Method

パターンの目的

p.116,line.3-5: オブジェクトを作成するときのインターフェイスだけを規定して、実際にどのクラスをインスタンス化するかはサブクラスが決めるようにする。 FactoryMethodパターンは、インスタンス化をサブクラスに任せる。

GoF パターンのクラス図

structure-08-05:factorymethod

MixJuice 版 Factory Method (改善)

解決される GoF パターンの問題点

情報隠蔽の問題点

p.118,line.17-18: factory methodが2つのクラス階層の間の関係をどのように定義しているかに注意してほしい。それはクラスの組み合わせに関する知識を局所化している。

補足: Factory Method によってクラスの組み合わせに関する知識を局所化しているのに、既存のプログラミング言語では、ソースコード上で必ずしも局所的に表現できない点が問題である。

対策

対応する ConcreteProduct と ConcreteCreator を同一モジュール内で定義する。

結果

ConcreteProduct と ConcreteCreate のペアごとに同一モジュール内で定義することにより、モジュール単位での開発・保守を容易にする。

サンプルコード

structure-08-09:factorymethod_sample

module framework {
  define abstract class Creator {
    define abstract Product createProduct();
    ...
  }
  define abstract class Product {...}
}
module productA extends framework {
  define class CreatorA extends Creator {
    Product createProduct() { return new ProductA(); }
    ...
  }
  define class ProductA extends Product {
    define ProductA(){...}
    ...
  }
}
module productB extends framework {
  define class CreatorB extends Creator {
    Product createProduct() { return new ProductB(); }
    ...
  }
  define class ProductB extends Product {
    define ProductB(){...}
    ...
  }
}

MixJuice 版 Factory Method (別解)

GoF パターンの問題点

型安全性の問題点
Factory Method パターンではオブジェクトを生成するメソッドをスーパークラスで宣言し、サブクラスで override することにより実際に生成するオブジェクトのクラスをカスタマイズする。ここで、このメソッドの型はスーパークラスで宣言した時点で決まるため、実際に生成するオブジェクトのクラスはメソッドの型には現れない。この結果、実際に生成するオブジェクト特有のメソッドを呼び出す必要がある場合にはダウンキャストが必要である。
クラス数増加の問題点

p.121, line 17-19:

  1. テンプレートを用いてサブクラス化を避ける。

先に述べたように、factory method の潜在的な問題点として、適切な ConcreteProduct オブジェクトを生成するためにサブクラスを作成しなければならない点をあげることができる。

カスタマイズするためにはサブクラスを作らなければならず、クラス数が増加する。

対策

ConcreteCreator を Creator のサブクラスとして実装するのではなく、差分として実装する。同様に ConcreteProduct を Product に対する差分として実装する。

factory method が生成したオブジェクトをインスタンス変数に保存する場合、 Creator は ConcreteProduct を知らないので、 Product 型の変数に代入することになる。しかし、ConcreteCreator で ConcreteProduct が必要な場合には、 (downcast を使わないためには)別のインスタンス変数が必要になる。同じオブジェクトを指すのにこれは無駄、また一貫性管理の必要性を生む。 MixJuice では、Product への差分として ConcreteProduct を実現すればひとつの変数で済む。

結果

構造

structure-08-09:factorymethod

module m {
  define class Product {
    define abstract Product();
  }
  define class Creator {
    define Product product;
    define void anOperation() { ... product = new Product(); ... }
  }
}

module m.implementation extends m {
  class Product {
    Product() {...}
    define void concreteMethod() {...}
  }
  class Creator {
    define void anotherOperation() {
      ...
      product.concreteMethod(); // キャスト不要
      ...
    }
  }
}

サンプルコード

Prototype

パターンの目的

生成すべきオブジェクトの種類を原型となるインスタンスを使って明確にし、それをコピーすることで新たなオブジェクトの生成を行なう。

GoF パターンのクラス図

structure-08-05:prototype

MixJuice 版 Prototype (改善)

解決される GoF パターンの問題点

導入可能性の問題点

p.131 「Prototype パターンの主な問題点として、prototype の各サブクラスが Clone オペレーションを実装しなければならず、これが困難な場合があるということがあげられる。たとえば、すでに存在しているクラスに対して Clone オペレーションを追加することは難しい。」

p.120, line.37-39, The main liability of the Prototype pattern is that each subclass of Prototype must implement the Clone operation, which may be difficult. For example, adding Clone is difficult when the classes under consideration already exist.

対策

既存のクラスに copy() メソッドを追加する。 (Java では clone() という名前はつかえない。)

結果

既存のクラスを Prototype パターンの一部として有効利用できる。

構造

structure-08-09:prototype

module original {
  define abstract class AbstractProduct {...}
  define class ConcreteProduct1 extends AbstractProduct {...}
  define class ConcreteProduct2 extends AbstractProduct {...}
}

module extension extends original {
  class AbstractProduct {
    define abstract AbstractProduct copy();
  }

  class ConcreteProduct1 {
    AbstractProduct copy() {...}
  }

  class ConcreteProduct2 {
    AbstractProduct copy() {...}
  }
}

サンプルコード

関連するパターン

Singleton

パターンの目的

p.137,line.3-4 あるクラスに対してインスタンスが1つしか依存しないことを保証し、それにアクセスするためのグローバルな方法を提供する。

GoF パターンのクラス図

structure-08-05:singleton

MixJuice 版 Singleton (別解)

解決される GoF パターンの問題点

クラス数増加の問題点

p.138,line.24-29: 5.クラスオペレーションよりも柔軟である。Singletonパターンと同等の機能を提供する別の方法として、クラスオペレーション(C++における静的メンバ関数、Smalltalkにおけるクラスメソッド)を用いることもできる。しかし、このような言語上のテクニックで対処する方法では、クラスのインスタンスを1つ以上に変更する場合に困難になる。さらに、C++における静的メンバ関数は仮想関数にすることができないので、サブクラスでポリモルフィックにオーバーライドすることができない。

p.128, line.26-31, The Singleton pattern has several benefits: 5. More flexible than class operations. Another way to package a singleton's functionality is to use class operations (that is, static member functions in C++ or class methods in Smalltalk). But both of these language techniques make it hard to change a design to allow more than one instance of a class. Moreover, static member functions in C++ are never virtual, so subclasses can't override them polymorphically.

補足:クラスオペレーション(Java における static method )を用いた方が、定義および呼び出しの記述が簡潔であるし、呼び出しの実行速度も(おそらく)速い、というメリットがある。 Singleton パターンを使うとこれらのメリットが失われる。

対策

MixJuice の将来のバージョンでは static method もモジュールの追加によって拡張可能なので、グローバルなオペレーションを普通に static method で定義すれば良い。(ただし MJ1.1 では static method の拡張はサポートされていない。)

結果

static method を使って、簡潔にグローバルかつ拡張可能なオペレーションが定義できる。その代わり、結果の節の4項目めに書いてある「インスタンスの数を変えることができる。」という Singleton パターンの利点の1つは享受できない。

構造

structure-08-09:singleton

module original {
  define class C {
    define static void operation() {...}
    define static int data;
  }
}

module extension extends original {
  class C {
    static void operation() { ... original() ...}
  }
}

サンプルコード

関連するパターン

Adapter

パターンの目的

p.149,line.3-5: あるクラスのインターフェイスを、クライアントが求める他のインターフェイスへ変換する。Adapterパターンは、インターフェイスに互換性のないクラス同士を組み合わせることができるようにする。

GoF パターンのクラス図

structure-08-05:adapter1

structure-08-05:adapter2

MixJuice 版 クラスに適用する Adapter (別解1)

解決される GoF パターンの問題点

拡張性の問題点

p.153 「adapter の潜在的な問題は、すべてのクライアントに対して透過性がないことである。適合されたオブジェクトは、もはや Adaptee クラスのインタフェースには従っていない。そのため、本来の Adaptee オブジェクトと同じように使用することはできなくなる。」

p.143, line.9-12, A potential problem with adapters is that they aren't transparent to all clients. An adapted object no longer conforms to the Adaptee interface, so, it can't be used as is whenever an Adaptee object can.

補足:既存ソースコード中の各サブクラスのインスタンス生成している箇所を、新たに追加したサブクラスの名前に変更しなければならない。

クラス数増加の問題点

p.152,line.11-13: Adaptee クラスの Target クラスへの適合を、具象クラス Adapter に任せる。結果として、あるクラス、およびそのすべてのサブクラスを適合させたいときに、クラスに適用する Adapter パターンではうまくいかない。

補足:具体的になにがうまくいかないのかを以下に説明する。既存のあるクラスが n 個のサブクラスを持っており、この n 個のクラスすべてを適合させたい場合を考える。この n 個のクラスを、さらにサブクラスすることによって、そのインターフェースを適合させるには n 個の新しいクラスを作らなければならずコード記述量が多い上、サブクラスが増えるたびにそれに対応する Adapter クラスも増やさなければならない。なお、適用可能性の節の3項目めに述べられているように、「オブジェクトに適用する Adapter パターン」ならば、 n 個のサブクラスすべてを一度に適合させることが可能である。

対策

スーパーインターフェースを追加する。これは GoF パターンの別解である。

結果

あるクラスおよびそのすべてのサブクラスのインターフェースを一度に適合させることができる。既存のソースコードの修正は不要である。

ただし、「両方向 adapter」は扱えない。これはクラスの多重継承が必要なためである。

構造

structure-08-09:adapter

module library {
  define class Adaptee {
    define void specificRequest() {...}
  }
  define class SubAdaptee extends Adaptee {...}
}
module application extends library {
  define interface Target {
    define void request();
  }
  class Adaptee implements Target {
    void request() { specificRequest(); }
  }
}

Bridge

パターンの目的

抽出されたクラスと実装を分離して、それらを独立に変更できるようにする。

GoF パターンのクラス図

structure-08-05:bridge

MixJuice 版 Bridge (改善)

解決される GoF パターンの問題点

拡張性の問題点
クラス Abstraction およびクラス Implementor で、十分なオペレーションをあらかじめ定義しておく必要がある。オペレーションをあとから追加するためには、ソースコードの修正が必要になる。例えば GoF 本の動機の章の例でいえば、 DrawText と DrawLine というオペレーションに加えて、あとから DrawImage というオペレーションが必要になることは容易に想像できる。(この問題点は GoF 本では指摘されていない。頻繁に起こり得る問題ではないだろうか?)

対策

クラス Abstraction へのメソッド追加および抽象クラス Implementor へのアブストラクトメソッド追加により、あとからオペレーションを追加できる。

結果

あらかじめ予想されていなかったオペレーションを追加することが可能になる。すべての ConcreteImplementor クラスに、追加されたオペレーションを実装する補完モジュールが必要になる。

構造

xxx

MixJuice 版 Bridge (別解)

解決される GoF パターンの問題点

型安全性の問題点
クラス数増加の問題点

対策

Bridge パターンが目的とする状況は Abstraction と Implementation を独立して選択できる場合である。

機能と実装の拡張
Implementation
X Window SystemPresentation Manager
Abstractionicon
transient

この独立性を実現するために、Bridge パターンでは Abstraction と Implementation のふたつのクラス階層を使う。しかし、MixJuice では、Implementation の拡張を差分によって実現することにより、ひとつのクラス階層で同様な拡張性を実現できる。

結果

ひとつのバイナリでひとつの Implementation しか使用しないのであれば、 MixJuice ではクラス階層をひとつで済ますことができる。

構造

structure-08-09:bridge

module original {
  define class Abstraction {
    define void operation() {
      ... implementationOperation1(); ...
    }

    define abstract void implementationOperation1();
    define abstract void implementationOperation2();
  }

  define class RefinedAbstractionA extends Abstraction {...}
  define class RefinedAbstractionB extends Abstraction {...}
}

module implementation1 extends original {
  class Abstraction {
    void implementationOperation1() {...}
    void implementationOperation2() {...}
  }
}

module implementation2 extends original {
  class Abstraction {
    void implementationOperation1() {...}
    void implementationOperation2() {...}
  }
}

サンプルコード

structure-08-09:bridge_sample

module window {
  define class Window {
    define void drawRect() {
      drawLine();
      drawLine();
      drawLine();
      drawLine();
    }

    define abstract void drawText();
    define abstract void drawLine();
  }

  define class IconWindow extends Window {...}
  define class TransientWindow extends Window {...}
}

module window.implementation.x extends window {
  class Window {
    void drawText() {... XDrawString(); ...}
    void drawLine() {... XDrawLine(); ...}
  }
}

module window.implementation.pm extends window {
  class Window {
    void drawText() {...}
    void drawLine() {...}
  }
}

関連するパターン

Composite

パターンの目的

部分−全体階層を表現するために、オブジェクトを木構造に組み立てる。 Composite パターンにより、クライアントは、個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができるようになる。

GoF パターンのクラス図

structure-08-05:composite

MixJuice 版 Composite (改善)

解決される GoF パターンの問題点

導入可能性の問題点
既存のクラスを Composite パターンの一部として扱えない。

対策

MixJuice では、スーパーインターフェース追加により後から Composite パターンの要素にできる。

結果

構造

module original {

define class C { ... }

} module extension extends original {

define interface Component { ... }
class C implements Component { ... }

}

Decorator

パターンの目的

p.187,line.3-4: オブジェクトに責任を動的に追加する。Decoratorパターンは、サブクラス化よりも柔軟な機能拡張方法を提供する。

GoF パターンのクラス図

structure-08-05:decorator

MixJuice 版 Decorator (改善)

解決される GoF パターンの問題点

拡張性の問題点

p.191,line.7-9: 1.インターフェイスの一致。decoratorのインターフェイスは、それが装飾するcomponentのインターフェイスと一致していなければならない。したがって、(少なくともC++では)ConcreteDecoratorクラスは1つの共通なクラスを継承しなければならない。

補足:あらかじめ決められたインターフェース以外のオペレーションを、 component と decorator にあとから追加できない点が問題である。

対策

抽象クラス Decorator に対して、メソッド追加あるいはアブストラクトメソッド追加を行なえばよい。

結果

component と decorator に、あとからオペレーションを追加できる。アブストラクトメソッド追加を行なった場合は、すべての ConcreteDecorator に、追加されたオペレーションを実装する補完モジュールが必要になる。しかしたいていの場合は、 Decorator クラスでデフォルトの実装として、次の decorator に処理を delegate するメソッド追加すれば十分なので、補完モジュールは不要であると思われる。

構造

structure-08-07:decorator2

module original {
  define abstract class Component {
    define abstract void operation();
  }
  define class ConcreteComponent extends Component {
    void operation(){...}
  }
  define abstract class Decorator extends Component {
    Component component;
    void operation(){ component.operation(); }
  }
  define class ConcreteDecorator extends Decorator {
    void operation(){...}
  }
}
module extension extends original {
  class Component {
    define abstract void newOperation();
  }
  class ConcreteComponent {
    void newOperation(){...}
  }
  class Decorator {
    void newOperation(){ component.newOperation(); }
  }
}

MixJuice 版 Decorator (別解)

解決される GoF パターンの問題点

クラス数増加の問題点

p.190,line.30-33: 3.decoratorとそれが装飾しているcomponentは同一ではない。decoratorは、透明な囲いのように振舞う。しかし、オブジェクトの同一性という観点では、装飾されたcomponentはcomponent自体と同一ではない。したがって、 decoratorを利用するときには、オブジェクトの同一性に依存すべきではない。

p.190,line34 -p.191,line.1-4: 4.多くの小さなオブジェクト。Decoratorパターンを利用して設計したシステムは、しばしば同じように見える多くの小さなオブジェクトから構成されることになる。それらのオブジェクトは相互に関連のしかたが異なるだけで、所属するクラスや変数の値は同じである。このようなシステムは、それを理解している人にとってはカスタマイズが容易だが、それを学んだりデバッグしたりするのは困難になることがある。

対策

もし、 component の振る舞いを動的に拡張したり、複数の振る舞いを持つ component をアプリケーション内で使う必要がなければ、 Decorator パターンを用いずに、 component そのものをクラス拡張によって拡張すれば良い。これは GoF パターンの別解である。

結果

オブジェクトの同一性が保証される。クラス数がへり、プログラムの構造が簡潔になる。 Decorator pattern では component に新たなメソッドを追加することはできないが、クラス拡張ならば可能である。動的に component の振る舞いを変更したり、異なる振る舞いをする複数の component を1つのアプリケーション内で使うことはことはできなくなる。などなど。

構造

structure-08-07:decorator1

module original {
  define class Component {
    define void operation();
  }
}
module extension1 extends original {
  class Component {
    void operation(){...}
  }
}
module extension2 extends original {
  class Component {
    void operation(){...}
  }
}

Facade

パターンの目的

p.196,line.3-5: サブシステム内に存在する複数のインターフェイスに1つの統一インターフェイスを与える。Facadeパターンはサブシステムの利用を容易にするための高レベルインターフェイスを定義する。

GoF パターンのクラス図

structure-08-05:facade

MixJuice 版 Facade (改善)

解決される GoF パターンの問題点

情報隠蔽の問題点

p.201,line.4-8 サブシステム内にあるクラスを非公開にすることは有用であろうが、そのような機構をサポートしているオブジェクト指向言語はほとんどない。C++と Smalltalkは、従来からクラスに対してグローバルなネームスペースを採用してきた。しかし最近C++標準化委員会は、この言語に、サブクラス内の公開クラスだけをサブシステム外部に見せることを可能にする新たなネームスペースを追加した。

対策

名前空間分離を用いれば良い。

結果

サブクラス内の公開クラスだけをサブシステム外部に見せることが可能になる。

構造

xxx

MixJuice 版 Facade (別解)

解決される GoF パターンの問題点

クラス数増加の問題点
Java のような言語では、既存のクラスは変更できないため、 Facade パターンにより新しくインターフェイスを導入するためには新しくクラスを作らなければならない。これはクラス数の増加を招く。

対策

MixJuice では既存のクラスにメソッドを追加でき、これにより、クラス数を増加させずに高レベルインターフェイスを導入できる。

結果

構造

structure-08-09:facade

module library {
  define class A {...}
  define class B {...}
  define class C {...}
}

module facade extends library {
  class A {
    define static void simpleMethod(...) {...}
  }
}

サンプルコード

関連するパターン

Flyweight

パターンの目的

多数の細かいオブジェクトを効率よくサポートするために共有を利用する。

GoF パターンのクラス図

structure-08-05:flyweight

Proxy

パターンの目的

あるオブジェクトへのアクセスを制御するために、そのオブジェクトの代理、または入れものを提供する。

GoF パターンのクラス図

structure-08-05:proxy

Chain of Responsibility

パターンの目的

p.237,line.3-5: 1つ以上のオブジェクトに要求を処理する機会を与えることにより、要求を送信するオブジェクトと受信するオブジェクトの結合を避ける。受信する複数のオブジェクトをチェーン状につなぎ、あるオブジェクトがその要求を処理するまで、そのチェーンに沿って要求を渡していく。

GoF パターンのクラス図

structure-08-05:chain

MixJuice 版 Chain of Responsibility (改善)

解決される GoF パターンの問題点

導入可能性の問題点
Chain of Responsibility パターンのクラスは要求を転送するためのインターフェースを持たなければならない。このため、そのインターフェースを持っていないクラスはパターンに参加できない。
拡張性の問題点

p.241,line.22-25: 3.要求を表現する。要求を表現するために別の選択が可能である。もっとも簡単な形式は、HandleHelpクラスの場合のように、要求をコーディングされたオペレーションの呼び出しとして実現する方法である。これは便利で簡単であるが、Handlerクラスが定義している要求しか転送できないことになる。

p.227, line.21-23, 3.Representing requests. Different operations are available for representing requests. In the simplest form, the request is a hard-coded operation invocation, as in the case of HandleHelp. This is convenient and safe, but you can forward only the fixed set of requests that the Handler class defines.

対策

Handler クラスに対するメソッド追加あるいはアブストラクトメソッド追加で解決できる。

結果

もともと Handlerクラスで定義していなかった要求も転送できるようになる。アブストラクトメソッド追加を行なった場合は、すべての ConcreteHandler に対してメソッド実装を追加する補完モジュールが必要になる。

また、もともと Chain of Responsibility パターンを使用していなくても、リスト状の構造があれば、Handler をスーパーインターフェースとして追加し、 HandleRequest メソッドを実装することによりパターンを導入できる。

構造

structure-08-09:chain

module original {
  define abstract class Handler {
    define abstract void handleRequestA();
  }

  define class ConcreteHandler1 extends Handler {
    void handleRequestA() {...}
  }

  define class ConcreteHandler2 extends Handler {
    void handleRequestA() {...}
  }
}

module extension extends original {
  class Handler {
    define abstract void handleRequestB();
  }

  class ConcreteHandler1 {
    void handleRequestB() {...}
  }

  class ConcreteHandler2 {
    void handleRequestB() {...}
  }
}

Command

パターンの目的

要求をオブジェクトとしてカプセル化することによって、異なる要求や、要求からなるキューやログにより、クライアントをパラメータ化する。また、取り消し可能なオペレーションをサポートする。

GoF パターンのクラス図

structure-08-05:command

Interpreter

パターンの目的

p.261,line.3: 言語に対して、文法表現と、それを使用して文を解釈するインタプリンタを一緒に定義する。

GoF パターンのクラス図

structure-08-05:interpreter

MixJuice 版 Interpreter (改善)

解決される GoF パターンの問題点

導入可能性の問題点
言語を表すクラス階層が既にあっても、 Interpreter パターンに必要なメソッドが定義されていなければ、後からそのクラス階層を Interpreter パターンとして利用することは難しい。
拡張性の問題点

p.265,line.11-15: 4.表現を解釈する新しい方法を追加する。Interpreterパターンは、表現を新しい方法で評価することを容易にする。たとえば、クラス上に新しいオペレーションを定義することにより、きれいな印刷や型チェックをサポートすることもできる。もし、表現を解釈する新しい方法を継続的に追加していこうとするならば、Visitorパターンを使って文法クラスに変更が及ぶのを避けるようにすることを考える。

対策

AbstractExpression に対するアブストラクトメソッド追加によって解決する

結果

Visitor パターンを使わなくても、新しいオペレーションを追加できる。このとき、すべての ConcreteExpression に対してメソッド実装を追加しなければならない。また、他のモジュールが新しい ConcreteExpresson を導入した場合には補完モジュールが必要になる。

また、抽象構文木があれば、その上に解釈メソッドを追加することにより、パターンを後から導入できる。

構造

structure-08-09:interpreter

module original {
  define class Client {
    AbstractExpression exp;
    Context context;
    define void m(){
      exp.interpret(context);
    }
  }
  define class Context {...}
  define abstract class AbstractExpression {
    define abstract void interpret(Context context);
  }
  define class ExpressionA extends AbstractExpression {
    void interpret(Context context){...}
  }
  define class ExpressionB extends AbstractExpression {
    void interpret(Context context){...}
  }
}
module extension extends original {
  class AbstractExpression {
    define abstract void newOperation();
  }
  class ExpressionA {
    void newOperation(){...}
  }
  class ExpressionB {
    void newOperation(){...}
  }
}

サンプルコード

structure-08-09:interpreter_sample

module expression {
  define abstract class Tree {...}
  define class Node extends Tree {...}
  define class Leaf extends Tree {...}
}

module macro_expantion extends expression {
  class Tree {
    define abstract Tree macroExpand();
  }
  class Node { Tree macroExpand() {...} }
  class Leaf { Tree macroExpand() {...} }
}

module eval extends expression {
  class Tree {
    define abstract Tree eval();
  }
  class Node { Tree eval() {...} }
  class Leaf { Tree eval() {...} }
}

関連するパターン

Iterator

パターンの目的

p.275,line.3-4: 集約オブジェクトが基にある内部表現を公開せずに、その要素を順にアクセスする方法を提供する。

GoF パターンのクラス図

structure-08-05:iterator

MixJuice 版 Iterator (改善)

解決される GoF パターンの問題点

情報隠蔽の問題点

p.280,line.24-29: しかし、そのような特権的なアクセス権は、新しい走査を定義するのを困難にする。なぜならば、それは別のフレンドクラスを追加することになるため、 aggregateのインターフェイスを変えなければならないからである。この問題を避けるために、aggregateの重要だが公開されていないメンバにアクセスするために、Iteratorクラスに保護的なオペレーションを入れておくこともできる。Iteratorのサブクラスは、aggregateに対して特権的なアクセス権を得るために、このオペレーションを使う(これはIteratorのサブクラスだけの特徴である)。

対策

Iterator が利用するオペレーションと、公開オペレーションの名前空間分離を行なう。

結果

Iterator が利用するオペレーションの他のモジュールが不用意に呼び出すことを避けることができる。

なお Java では JDK1.1 では package 、 JDK1.2 以降では nested class を使って、 Iterator 用のオペレーションを情報隠蔽している。しかし、この方法では、・・・という問題がある。

構造

structure-08-09:iterator

module aggregate {
  define class Aggregate {
    define abstract Iterator createIterator();
  }

  define class Iterator {
    define abstract void first();
    define abstract void next();
    define abstract boolean isDone();
    define abstract Object currentItem();
  }
}

module aggregate.implementation extends aggregate {
  define class ConcreteAggregate extends Aggregate {
    Iterator createIterator() {...}
  }
  define class ConcreteIterator extends Iterator {
    // ConcreteAggregate の内容を直接アクセス
    void first() {...}
    void next() {...}
    boolean isDone() {...}
    Object currentItem() {...}
  }
}

Mediator

パターンの目的

オブジェクト群の相互作用をカプセル化するオブジェクトを定義する。 Mediator パターンは、オブジェクト同士がお互いを明示的に参照し合うことがないようにして、結合度を低めることを促進する。それにより、オブジェクトの相互作用を独立に変えることができるようになる。

GoF パターンのクラス図

structure-08-05:mediater

Memento

パターンの目的

p.303, line.3-4 カプセル化を破壊せずに、オブジェクトの内部状態を据えて外面化しておき、オブジェクトを後にこの状態に戻すことができるようにする。

GoF パターンのクラス図

structure-08-05:memento

MixJuice 版 Memento (改善)

解決される GoF パターンの問題点

情報隠蔽の問題点

p.307,line.8-10 4.narrowインターフェイスとwideインターフェイスを定義する。言語によっては、OriginatorオブジェクトだけがMementoオブジェクトの状態にアクセスできるようにするのは難しいかもしれない。

p.307 「Memento クラスには 2つのインタフェースがある。すなわち、Originator オブジェクトのための wide インタフェースと、他のオブジェクトのための narrow インタフェースである。」

p.287, line.5-6, Mementos have two interfaces: a wide one for originators and a narrow one for other objects.

対策

名前空間分離によって解決できる。

結果

OriginatorオブジェクトだけがMementoオブジェクトの状態にアクセスできるようになる。

構造

structure-08-09:memento

module m {
  define class Originator {
    define abstract void setMemento(Memento m);
    define abstract Memento createMemento();
  }

  define class Memento {}
}

module m.implementation extends m {
  class Originator {
    int state;
    void setMemento(Memento m) {...}
    Memento createMemento() {...}
  }

  class Memento {
    int state;
  }
}

Observer

パターンの目的

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

GoF パターンのクラス図

structure-08-05:observer

MixJuice 版 Observer (改善)

解決される GoF パターンの問題点

導入可能性の問題点
既存のクラスを Subject や Observer として利用することができない。
拡張性の問題点

p.318:

  1. observer ごとに更新プロトコルが異なるようなことは避ける(push モデルと pull モデル)。

Observer パターンの実装では、しばしば subject に、変化についての付加的な情報をブロードキャストさせるようにする。 subject はこの情報を Update オペレーションの引数として渡す。やりとりの方法により、情報量にはかなりの差が生じるだろう。

ここで observer の種類により、必要な情報は異なるが、 Java では update オペレーションの引数が後から変更できないため、適切に情報を渡せない。

対策

Observer の登録機構を別モジュールで定義する。これにより、既存のクラスを Subject/Observer として利用できるようになる。

また、拡張性の問題に対しては、必要に応じて、新しい update オペレーションを定義する。 MixJuice では既存のクラスを後から変更できるのでこれが可能である。

結果

構造

structure-08-09:observer

module original {
  define class Subject {
    define void sideEffectOperation() {...}
  }
}

module extension extends original {
  define abstract class Observer {
    define abstract void update();
  }

  class Subject {
    define void attach(Observer o) {...}
    define void detach(Observer o) {...}
    define void changed() {...}

    void sideEffectOperation() {
      original();
      changed();
    }
  }
}

サンプルコード

関連するパターン

State

パターンの目的

オブジェクトの内部状態が変化した時に、オブジェクトが振舞いを変えるようにする。クラス内では、振舞いの変化を記述せず、状態を表すオブジェクトを導入することでこれを実現する。

GoF パターンのクラス図

structure-08-05:state

MixJuice 版 State (改善)

解決される GoF パターンの問題点

拡張性の問題点
クラス State で、十分なオペレーションをあらかじめ定義しておく必要がある。例えば GoF 本の動機の章の例でいえば、 Open, Close, Acknowledge というオペレーションに加えて、あとから Flush というオペレーションが必要になることは容易に想像できる。(この問題点は GoF 本では指摘されていない。頻繁に起こり得る問題ではないだろうか?)

対策

抽象クラス State へのアブストラクトメソッド追加により、あとからオペレーションを追加できる。

結果

あらかじめ予想されていなかったオペレーションを追加することが可能になる。すべての ConcreteState クラスに、追加されたオペレーションを実装する補完モジュールが必要になる。

構造

structure-08-07:state

module original {
  define class Context {
    State state;
    define void request(){
      state.handle();
    }
  }
  define abstract class State {
    define abstract void handle();
  }
  define class ConcreteStateA extends State {
    void handle(){...}
  }
  define class ConcreteStateB extends State {
    void handle(){...}
  }
}
module extension extends original {
  class State {
    define abstract void newOperation();
  }
  class ConcreteStateA {
    void newOperation(){...}
  }
  class ConcreteStateB {
    void newOperation(){...}
  }
}

Strategy

パターンの目的

アルゴリズムの集合を定義し、各アルゴリズムをカプセル化して、それらを交換可能にする。 Strategy パターンを利用することで、アルゴリズムを、それを利用するクライアントからは独立に変更することができるようになる。

GoF パターンのクラス図

structure-08-05:strategy

MixJuice 版 Strategy (改善)

解決される GoF パターンの問題点

拡張性の問題点

p.339 「6. Strategy クラスと Context クラス間の通信に関するオーバーヘッド。... このことから、いくつかの ConcreteStrategy クラスが、このインタフェースを通して送られるすべての情報を利用しないことは十分にありうる。... もしこれが問題ならば、Strategy クラスと Context クラスの間により密な結合が必要になる。」

p.318, line.17-23, 6.Communication overhead between Strategy and Context. The Strategy interface is shared by all Concrete Strategy classes whether the algorithms they implement are trivial or complex. Hence, it's likely that some ConcreteStrategies won't use all the information passed to them through this interface; simple ConcreteStrategies may use none of it. That means there will be times when the context creates and initializes parameters that never get used. If this is an issue, then you'll need tighter coupling between Strategy and Context.

p.339 「1. Strategy クラスと Context クラスのインタフェースの定義。 Strategy クラスと Context クラスのインタフェースは、 Context オブジェクトのどのようなデータに対しても、 ConcreteStrategy オブジェクトが効果的にアクセスできるようにしなければならない。... 特定のアルゴリズムの必要性やそれに対するデータ要求に応じて最善の方法を選ぶことになる。」

p.319, line.5-19, 1.Defining the Strategy and Context interfaces. The Strategy and Context interfaces must give a ConcreteStrategy efficient access to any data it needs form a context, and vice versa. One approach is to have Context pass data in parameters to Strategy operations--in other words, take the data to the strategy. This keeps Strategy and Context decoupled. On the other hand, Context might pass data the Strategy doesn't need. Another technique has a context pass itself as an argument and the strategy requests data from the context explicitly. Alternatively, the strategy can store a reference to its context, eliminating the need to pass anything at all. Either way, the strategy can request exactly what it needs. But now Context must define a more elaborate interface to its data, which couples Strategy and Context more closely. The needs of the particular algorithm and its data requirements will determine the best technique.

個々の ConcreteStrategy が存在しない時点で、最善の結合を設計するのは難しい。このため、オーバヘッドが大きくなりがちである。

対策

差分により、結合のインタフェースを必要に応じて増強する。つまり(最善の)インタフェースを選ぶタイミングを遅らせることができる。

結果

Strategy に差分で抽象メソッドを追加できることにより、 Context から Strategy へ渡す情報を後から増やすことができる。ただし、オブジェクト毎にインタフェースを選べるわけではなく、システムに必要なもっとも密な結合になる。

構造

xxx

MixJuice 版 Strategy (別解)

解決される GoF パターンの問題点

クラス数増加の問題点

p.340 「2. テンプレートパラメータとしての Strategy クラス」

p.319, line.20, 2.Strategies as template parameters. In C++ templates can be used to configure a class with a strategy. This technique is only applicable if (1) the Strategy can be selected at compile-time, (2) it does not have to be changed at run-time.

一つのシステムで一種類の ConcreteStrategy しか必要ない場合、 Context と ConcreteStrategy を別のオブジェクトにすることは不必要な複雑さを導入することになる。

対策

Context への差分として ConcreteStrategy に相当する実装を行なう。

結果

Strategy, ConcreteStrategy が必要なくなり、クラス数が減る。

構造

xxx

サンプルコード

関連するパターン

Template Method

パターンの目的

1つのオペレーションにアルゴリズムのスケルトンを定義しておき、その中のいくつかのステップについては、サブクラスでの定義に任せることにする。 Template Method パターンでは、アルゴリズムの構造を変えずに、アルゴリズム中のあるステップをサブクラスで再定義する。

GoF パターンのクラス図

structure-08-05:templatemethod

MixJuice 版 Template Method (改善)

解決される GoF パターンの問題点

拡張性の問題点

p.351 「2. primitive operation の最小化」

p.329, line.4-7, 2.Minimizing primitive operations. An important goal in designing template methods is to minimize the number of primitive operations that a subclass must override to flesh out the algorithm. The more operations that need overriding, the more tedious things get for clients.

クラス数増加の問題点

対策

hook operation の実装を差分として提供する。

hook operation をサブクラスで拡張するのではなく、差分で拡張することにより、可能な拡張の組合せを増やせる。ただし、ひとつのシステム内ではひとつの組合せしか利用できなくなる。

デフォルトの挙動にするわけにはいかないがそれなりに一般的な実装を差分により与えることができるため、無理に最小化しなくても利用のしやすさが低下しなくて済む。

構造

xxx

サンプルコード

関連するパターン

Visitor

パターンの目的

p.353,line.3-5: あるオブジェクト構造上の要素で実行されるオペレーションを表現する。 Visitorパターンにより、オペレーションを加えるオブジェクトのクラスに変更を加えずに新しいオペレーションを定義することができるようになる。

GoF パターンのクラス図

structure-08-05:visitor

MixJuice 版 Visitor (改善1)

解決される GoF パターンの問題点

情報隠蔽の問題点

p.359,line.5-8: 6.カプセル化を破る。visitorによるアプローチでは、ConcreteElementクラスのインターフェイスが、visitorが仕事を行うのに十分、強力であることを仮定している。その結果、このパターンでは要素の内部状態にアクセスする公開オペレーションを提供するように強いられることがしばしばある。したがって、カプセル化に対して妥協を与えることになるかもしれない。

対策

ConcreteElement クラスの公開オペレーションと内部状態へのアクセスオペレーションをそれぞれ仕様モジュールと実装モジュールに分離する。この対策は GoF パターンの場合でも別解の場合でも適用できる。

結果

ConcreteElement の内部状態へのアクセス方法を、公開オペレーションとは分離できる。しかし、 ConcreteElement の追加、アルゴリズムの追加のいずれの場合も補完モジュールが必要になる。

構造

structure-08-07:visitor

module element {
  define abstract class Element {
    define abstract void accept(Visitor visitor);
  }
  define class ConcreteElementA extends Element {
    void accept(Visitor v){ v.visitConcreteElementA(this); }
  }
  define class ConcreteElementB extends Element {
    void accept(Visitor v){ v.visitConcreteElementB(this); }
  }
  define abstract class Visitor {
    define abstract void visitConcreteElementA(ConcreteElementA a);
    define abstract void visitConcreteElementB(ConcreteElementB a);
  }
}
module element.implementation extends element {
  class ConcreteElementA {
    int state;
  }
  class ConcreteElementB {
    int state;
  }
}
module visitor extends element {
  define class ConcreteVisitor extends Visitor {}
}
module visitor.implementation
  extends visitor, element.implementation {
  class ConcreteVisitor {
    void visitConcreteElementA(ConcreteElementA a){
      ... int x = a.state; ...
    }
    void visitConcreteElementB(ConcreteElementB b){
      ... int x = b.state; ...
    }
  }
}

MixJuice 版 Visitor (改善2)

解決される GoF パターンの問題点

導入可能性の問題点
既存のデータ構造があっても、 accept メソッドが実装されていなければ Visitor パターンを使うことはできない。
拡張性の問題点

p.358 「3. 新しい ConcreteElement クラスを加えることは難しい」

p.336, line.4, 3.Adding new ConcreteElement classes is hard. The Visitor pattern makes it hard to add new subclasses of Element. Each new Concrete Element gives rise to a new abstract operation on Visitor and a corresponding implementation in every ConcreteVisitor Class.

対策

MixJuice では、メソッド追加により Visitor パターンを後から導入できる。

また、MixJuice ではアブストラクトメソッド追加により、ConcreteElement クラスを加えることができる。

結果

構造

structure-08-09:visitor2

module element {
  define abstract class Element {...}
  define class ConcreteElementA extends Element {...}
  define class ConcreteElementB extends Element {...}
}

module visitor extends element {
  class Element { define abstract void accept(Visitor v); }
  class ConcreteElementA { void accept(Visitor v) { v.visit(this); } }
  class ConcreteElementB { void accept(Visitor v) { v.visit(this); } }

  define class Visitor {
    define abstract void visit(ConcreteElementA elt);
    define abstract void visit(ConcreteElementB elt);
  }

  define class ConcreteVisitor1 extends Visitor {
    void visit(ConcreteElementA elt) {...}
    void visit(ConcreteElementB elt) {...}
  }

  define class ConcreteVisitor1a extends ConcreteVisitor1 {
    void visit(ConcreteElementA elt) {...}
    void visit(ConcreteElementB elt) {...}
  }
}

module new_concrete_element extends visitor {
  define class ConcreteElementC extends Element {
    ...
    void accept(Visitor v) { v.visit(this); }
  }

  class Visitor {
    define abstract void visit(ConcreteElementC elt);
  }

  class ConcreteVisitor1 {
    void visit(ConcreteElementC elt) {...}
  }

  class ConcreteVisitor1a {
    void visit(ConcreteElementC elt) {...}
  }
}

MixJuice 版 Visitor (別解)

解決される GoF パターンの問題点

クラス数増加の問題点
Visitor クラスおよびそのサブクラスはオペレーションを表現するものであり、これらをクラスとするのは適切ではない。したがって、これらのクラスの数だけ必要以上にクラス数が増加しているといえる。

対策

MixJuice では、Visitor パターンを使わなくても既存のモジュールを変更せずに新しいオペレーションを定義することができる。このように表現することにより、 Visitor パターンを使う場合と比較して Visitor クラスや accept メソッドが必要なくなり、クラス数・メソッド数が減少し、システムを単純化できる。

結果

Visitor パターンを使う必要がなく、Visitor クラスが必要なくなり、記述を単純にできる

ただし、ConcreteVisitor クラスの定義において継承を利用する場合は、この方法は使えない。

構造

The Visitor pattern without Visitor class

module element {
  define abstract class Element {...}
  define class ConcreteElementA extends Element {...}
  define class ConcreteElementB extends Element {...}
}

module traverser extends element {
  class Element { define abstract void traverse(); }
  class ConcreteElementA { void traverse() {...} }
  class ConcreteElementB { void traverse() {...} }
}

サンプルコード

関連するパターン


MixJuice によるデザインパターン改善カタログ


田中 哲 <akr@m17n.org>, 一杉 裕志 <y-ichisugi@aist.go.jp>