デザインパターンはソフトウェアの拡張性・再利用性を高める構成法のカタログである。しかし、拡張性・再利用性を指向しているのはデザインパターンだけではなく、言語自身も拡張性・再利用性を意図して設計されることがあり、そのような言語で既存のデザインパターンを使うと、拡張性・再利用性にさまざまな(往々にして良い)影響が現れる。ここでは、Java をベースとして拡張性・再利用性を指向した差分ベースモジュールという機構を持つ言語である MixJuice について、そのデザインパターン(GoF パターン 23種)への影響を述べる。
従来のオブジェクト指向言語で各パターンを使用する際に起きる問題点のうち、 MixJuice を用いることで改善できるものを表にまとめた。なお、 MixJuice による改善策の中にはトレードオフがあるものもある。 (表内のページ数は MixJuice が改善する問題点が、「デザインパターン」日本語版のどのページで述べられているかを示すものである。詳細については各パターンの説明のページを参照。)
| デザインパターン | 種別 | 導入 | 拡張 | 情報隠蔽 | 型安全性 | 単純化 | 使用するプログラミング技法 |
|---|---|---|---|---|---|---|---|
| AbstractFactory | 改善 | p.98 | アブストラクトメソッド追加 | ||||
| 別解 | p.98 | p.100 | ○ | 実装モジュール選択 | |||
| Builder | 改善 | p109 | ○ | メソッド追加・メソッド拡張 | |||
| 別解 | ○ | メソッド拡張・実装モジュール選択 | |||||
| FactoryMethod | 改善 | p.118 | 名前空間分離 | ||||
| 別解 | ○ | ○ | 実装モジュール選択 | ||||
| Prototype | 改善 | p.131 | アブストラクトメソッド追加 | ||||
| Singleton | 別解 | p.138 | クラスメソッド拡張 | ||||
| Adapter | 別解 | p.153 | p.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 | 改善1 | p.359 | 仕様モジュールと実装モジュールの分離 | ||||
| 改善2 | ○ | p.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 以外にも同様な技法を使える言語がある。たとえば、CLOS ではメソッド追加が可能である。そのような言語では MixJuice と同様なデザインパターンの改善が行なえることがある。
| 技法 | AspectJ | CLOS | Ruby |
| メソッド拡張 | around advice | alias/def | |
| スーパーインターフェース追加 | declare parents | defmethod | include |
| フィールド追加 | introduction | 代入 | |
| メソッド追加 | defmethod | class が実行文 | |
| アブストラクトメソッド追加 | 不要/メソッド追加と同様 | ||
| 名前空間分離 | aspect | package | |
| 仕様モジュールと実装モジュールの分離 | |||
| 実装モジュール選択 | aspect選択 | require 切替え | |
Aspect-Oriented Design Pattern Implementations - Language-dependence of Design Patterns
AspectJ による GoF デザインパターンの実装
不適切な点などがありましたら、mj-language ML または田中哲 <akr@m17n.org> まで連絡して下さい。
p.95, line.3-4: 互いに関連したり依存し合うオブジェクト群を、その具象クラスを明確にせずに生成するためのインタフェースを提供する。

p.98, line.10-15: 4.新たな種類の部品に対応することが困難である。新たな種類の部品に対応できるようにAbstractFactoryパターンを拡張することは容易ではない。なぜならば、AbstractFactoryクラスのインターフェイスは生成される部品の集合を固定しているからである。新たな種類の部品への対応は、インターフェイスの修正が必要となるために、AbstractFactoryクラスだけでなくそのすべてのサブクラスを修正しなければならなくなる。[実装]の節で、この問題に対する1つの解決策について議論する。
補足:最後に触れられている「1つの解決策」とは、実装の節の3項目めに書いてある。オブジェクトを生成するオペレーションに生成すべきクラスを指定するパラメータを付加するというもの。この方法を使うと返値のダウンキャストが必要になる。
AbstractFactory クラスに対するアブストラクトメソッド追加で対処可能である。
既存のソースコードを修正しなくても、新たな種類の部品の生成に対応することが可能になる。しかし、既存のすべての ConcreteFactory に対し追加された method を実装する補完モジュールが必要となる。

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();
}
}
}
p.98 「4. 新たな種類の部品に対応することが困難である。」
新たな種類の部品に対応するためには、AbstractFactory クラスの修正が必要になる。
この問題に対処するために、引数で種類を指定するという方法が述べられているが、この方法は静的型にそぐわないという問題がある。
p.100, line.12-18: すなわち、すべての部品が、戻り値の型により与えられる同一の抽象化されたインタフェースを備えた形でクライアントに返される。したがって、クライアントは部品のクラスに関して区別をしたり、安全な仮定をおくことが不可能になる。もし、クライアントがサブクラスに特有のオペレーションを実行したくても、抽象クラスのインタフェースを通してでは、それらを実行することはできない。クライアントはダウンキャストを(たとえば C++ の dynamic_cast を使って)行なうことができるかも知れないが、これは常に実行可能、または安全であるとは限らない。なぜならば、ダウンキャストは失敗する可能性があるからである。
生成された対象の内容をアクセスするためにダウンキャストが必要になりがちである。 GoF のウインドウシステムの例でいえば、ウインドウにピックスマップを背景として張り付ける場合、ウインドウオブジェクトはピックスマップオブジェクトのシステム依存な情報を得るためにダウンキャストを必要とする。
実装モジュール選択を使う。
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.

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() ...
}

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

p.108,line.35 - p.109,line1-3: 1.生成と組み立てのインターフェイス。ConcreteBuilderクラスは、Product オブジェクトを段階的に作成する。ゆえに、Builderクラスのインターフェイスは、あらゆる種類のConcreteBuilderクラスでのProductオブジェクトの作成に適応できるように、十分に一般的でなければならない。
補足:設計段階で予期していなかった種類の Product オブジェクトの作成が必要になった時、 Builder クラスが提供されているインタフェースでは不十分な場合がある。
Builder クラスに新たなメソッド(例えば buildPartC )を追加し、 Director から呼べるようにする。このとき、追加したメソッドを abstract method にする方法と、中身が空のメソッドにする方法がある。前者の場合、既存のすべての ConcreteBuilder に buildPartC の実装を追加するための補完モジュールが必要になる。
Builder クラスであらかじめ定義した生成のためのインターフェースが不十分であった場合にでも、あとからインターフェースを拡張できる。

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();
}
}
}
コメントを無視するパーザから、コメントノードを生成するパーザへの拡張。
GoF 本では RTF からさまざまな形式への変換する TextConverter が例として述べられている。この例では、 RTF の読み込み部を共通とし、書き出し部を可変にするために Builder パターンが使われている。
TextConverter の例において、各書き出し形式毎に rtf2ascii, rtf2tex, rtfview といった複数のバイナリを生成する場合には、個々のコマンド毎に静的に書き出し部が決定する。この場合、MixJuice では書き出し部分を RTFReader への差分として各種の実装を行なえ、出力部分をクラスとする必要がなくなり、クラス階層の単純化が可能である。
MixJuice では ConcreteBuilder, Builder を Director をまとめてひとつのクラスにしても、同じ生成過程で異なる表現形式のオブジェクトを生成できるようにするというパターンの目的は達成できる。
なお、ひとつのバイナリ内で出力形式を切替えたい時には複数の書き出し部をバイナリ内に共存させなければならないため、このように差分を使用することはできない。

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() { ... }
}
}

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

p.118,line.17-18: factory methodが2つのクラス階層の間の関係をどのように定義しているかに注意してほしい。それはクラスの組み合わせに関する知識を局所化している。
補足: Factory Method によってクラスの組み合わせに関する知識を局所化しているのに、既存のプログラミング言語では、ソースコード上で必ずしも局所的に表現できない点が問題である。
対応する ConcreteProduct と ConcreteCreator を同一モジュール内で定義する。
ConcreteProduct と ConcreteCreate のペアごとに同一モジュール内で定義することにより、モジュール単位での開発・保守を容易にする。

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(){...}
...
}
}
p.121, line 17-19:
- テンプレートを用いてサブクラス化を避ける。
先に述べたように、factory method の潜在的な問題点として、適切な ConcreteProduct オブジェクトを生成するためにサブクラスを作成しなければならない点をあげることができる。
カスタマイズするためにはサブクラスを作らなければならず、クラス数が増加する。
ConcreteCreator を Creator のサブクラスとして実装するのではなく、差分として実装する。同様に ConcreteProduct を Product に対する差分として実装する。
factory method が生成したオブジェクトをインスタンス変数に保存する場合、 Creator は ConcreteProduct を知らないので、 Product 型の変数に代入することになる。しかし、ConcreteCreator で ConcreteProduct が必要な場合には、 (downcast を使わないためには)別のインスタンス変数が必要になる。同じオブジェクトを指すのにこれは無駄、また一貫性管理の必要性を生む。 MixJuice では、Product への差分として ConcreteProduct を実現すればひとつの変数で済む。

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(); // キャスト不要
...
}
}
}
生成すべきオブジェクトの種類を原型となるインスタンスを使って明確にし、それをコピーすることで新たなオブジェクトの生成を行なう。

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 パターンの一部として有効利用できる。

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

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つは享受できない。

module original {
define class C {
define static void operation() {...}
define static int data;
}
}
module extension extends original {
class C {
static void operation() { ... original() ...}
}
}
p.149,line.3-5: あるクラスのインターフェイスを、クライアントが求める他のインターフェイスへ変換する。Adapterパターンは、インターフェイスに互換性のないクラス同士を組み合わせることができるようにする。


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」は扱えない。これはクラスの多重継承が必要なためである。

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(); }
}
}
抽出されたクラスと実装を分離して、それらを独立に変更できるようにする。

クラス Abstraction へのメソッド追加および抽象クラス Implementor へのアブストラクトメソッド追加により、あとからオペレーションを追加できる。
あらかじめ予想されていなかったオペレーションを追加することが可能になる。すべての ConcreteImplementor クラスに、追加されたオペレーションを実装する補完モジュールが必要になる。
xxx
Bridge パターンが目的とする状況は Abstraction と Implementation を独立して選択できる場合である。
| Implementation | ||||
| X Window System | Presentation Manager | ‥ | ||
| Abstraction | icon | |||
| transient | ||||
| : | ||||
この独立性を実現するために、Bridge パターンでは Abstraction と Implementation のふたつのクラス階層を使う。しかし、MixJuice では、Implementation の拡張を差分によって実現することにより、ひとつのクラス階層で同様な拡張性を実現できる。
ひとつのバイナリでひとつの Implementation しか使用しないのであれば、 MixJuice ではクラス階層をひとつで済ますことができる。

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() {...}
}
}

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 パターンにより、クライアントは、個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができるようになる。

MixJuice では、スーパーインターフェース追加により後から Composite パターンの要素にできる。
module original {
define class C { ... }
} module extension extends original {
define interface Component { ... }
class C implements Component { ... }
}
p.187,line.3-4: オブジェクトに責任を動的に追加する。Decoratorパターンは、サブクラス化よりも柔軟な機能拡張方法を提供する。

p.191,line.7-9: 1.インターフェイスの一致。decoratorのインターフェイスは、それが装飾するcomponentのインターフェイスと一致していなければならない。したがって、(少なくともC++では)ConcreteDecoratorクラスは1つの共通なクラスを継承しなければならない。
補足:あらかじめ決められたインターフェース以外のオペレーションを、 component と decorator にあとから追加できない点が問題である。
抽象クラス Decorator に対して、メソッド追加あるいはアブストラクトメソッド追加を行なえばよい。
component と decorator に、あとからオペレーションを追加できる。アブストラクトメソッド追加を行なった場合は、すべての ConcreteDecorator に、追加されたオペレーションを実装する補完モジュールが必要になる。しかしたいていの場合は、 Decorator クラスでデフォルトの実装として、次の decorator に処理を delegate するメソッド追加すれば十分なので、補完モジュールは不要であると思われる。

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(); }
}
}
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つのアプリケーション内で使うことはことはできなくなる。などなど。

module original {
define class Component {
define void operation();
}
}
module extension1 extends original {
class Component {
void operation(){...}
}
}
module extension2 extends original {
class Component {
void operation(){...}
}
}
p.196,line.3-5: サブシステム内に存在する複数のインターフェイスに1つの統一インターフェイスを与える。Facadeパターンはサブシステムの利用を容易にするための高レベルインターフェイスを定義する。

p.201,line.4-8 サブシステム内にあるクラスを非公開にすることは有用であろうが、そのような機構をサポートしているオブジェクト指向言語はほとんどない。C++と Smalltalkは、従来からクラスに対してグローバルなネームスペースを採用してきた。しかし最近C++標準化委員会は、この言語に、サブクラス内の公開クラスだけをサブシステム外部に見せることを可能にする新たなネームスペースを追加した。
名前空間分離を用いれば良い。
サブクラス内の公開クラスだけをサブシステム外部に見せることが可能になる。
xxx
MixJuice では既存のクラスにメソッドを追加でき、これにより、クラス数を増加させずに高レベルインターフェイスを導入できる。

module library {
define class A {...}
define class B {...}
define class C {...}
}
module facade extends library {
class A {
define static void simpleMethod(...) {...}
}
}
多数の細かいオブジェクトを効率よくサポートするために共有を利用する。

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

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

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 メソッドを実装することによりパターンを導入できる。

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

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

p.265,line.11-15: 4.表現を解釈する新しい方法を追加する。Interpreterパターンは、表現を新しい方法で評価することを容易にする。たとえば、クラス上に新しいオペレーションを定義することにより、きれいな印刷や型チェックをサポートすることもできる。もし、表現を解釈する新しい方法を継続的に追加していこうとするならば、Visitorパターンを使って文法クラスに変更が及ぶのを避けるようにすることを考える。
AbstractExpression に対するアブストラクトメソッド追加によって解決する
Visitor パターンを使わなくても、新しいオペレーションを追加できる。このとき、すべての ConcreteExpression に対してメソッド実装を追加しなければならない。また、他のモジュールが新しい ConcreteExpresson を導入した場合には補完モジュールが必要になる。
また、抽象構文木があれば、その上に解釈メソッドを追加することにより、パターンを後から導入できる。

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(){...}
}
}

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

p.280,line.24-29: しかし、そのような特権的なアクセス権は、新しい走査を定義するのを困難にする。なぜならば、それは別のフレンドクラスを追加することになるため、 aggregateのインターフェイスを変えなければならないからである。この問題を避けるために、aggregateの重要だが公開されていないメンバにアクセスするために、Iteratorクラスに保護的なオペレーションを入れておくこともできる。Iteratorのサブクラスは、aggregateに対して特権的なアクセス権を得るために、このオペレーションを使う(これはIteratorのサブクラスだけの特徴である)。
Iterator が利用するオペレーションと、公開オペレーションの名前空間分離を行なう。
Iterator が利用するオペレーションの他のモジュールが不用意に呼び出すことを避けることができる。
なお Java では JDK1.1 では package 、 JDK1.2 以降では nested class を使って、 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 パターンは、オブジェクト同士がお互いを明示的に参照し合うことがないようにして、結合度を低めることを促進する。それにより、オブジェクトの相互作用を独立に変えることができるようになる。

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

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オブジェクトの状態にアクセスできるようになる。

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

p.318:
- observer ごとに更新プロトコルが異なるようなことは避ける(push モデルと pull モデル)。
Observer パターンの実装では、しばしば subject に、変化についての付加的な情報をブロードキャストさせるようにする。 subject はこの情報を Update オペレーションの引数として渡す。やりとりの方法により、情報量にはかなりの差が生じるだろう。
ここで observer の種類により、必要な情報は異なるが、 Java では update オペレーションの引数が後から変更できないため、適切に情報を渡せない。
Observer の登録機構を別モジュールで定義する。これにより、既存のクラスを Subject/Observer として利用できるようになる。
また、拡張性の問題に対しては、必要に応じて、新しい update オペレーションを定義する。 MixJuice では既存のクラスを後から変更できるのでこれが可能である。

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 へのアブストラクトメソッド追加により、あとからオペレーションを追加できる。
あらかじめ予想されていなかったオペレーションを追加することが可能になる。すべての ConcreteState クラスに、追加されたオペレーションを実装する補完モジュールが必要になる。

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 パターンを利用することで、アルゴリズムを、それを利用するクライアントからは独立に変更することができるようになる。

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

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

p.359,line.5-8: 6.カプセル化を破る。visitorによるアプローチでは、ConcreteElementクラスのインターフェイスが、visitorが仕事を行うのに十分、強力であることを仮定している。その結果、このパターンでは要素の内部状態にアクセスする公開オペレーションを提供するように強いられることがしばしばある。したがって、カプセル化に対して妥協を与えることになるかもしれない。
ConcreteElement クラスの公開オペレーションと内部状態へのアクセスオペレーションをそれぞれ仕様モジュールと実装モジュールに分離する。この対策は GoF パターンの場合でも別解の場合でも適用できる。
ConcreteElement の内部状態へのアクセス方法を、公開オペレーションとは分離できる。しかし、 ConcreteElement の追加、アルゴリズムの追加のいずれの場合も補完モジュールが必要になる。

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; ...
}
}
}
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 クラスを加えることができる。

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 パターンを使わなくても既存のモジュールを変更せずに新しいオペレーションを定義することができる。このように表現することにより、 Visitor パターンを使う場合と比較して Visitor クラスや accept メソッドが必要なくなり、クラス数・メソッド数が減少し、システムを単純化できる。
Visitor パターンを使う必要がなく、Visitor クラスが必要なくなり、記述を単純にできる
ただし、ConcreteVisitor クラスの定義において継承を利用する場合は、この方法は使えない。

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() {...} }
}