mpdosaka
mpdosaka copied to clipboard
第6章 ソリューションドメイン分析
この章では再びドメイン分析について考えていくが、今度はアプリケーションドメインではなくソリューションドメインを扱う。アプリケーションドメイン分析の原則の多くが、ソリューション構造にも同様に適用される。ここで調査するソリューションドメインは、C++プログラミング言語そのものである。本書では、パターンも、ソリューションドメインにおける強力な武器だと見なす。しかし、ほとんどのパターンは単に共通性と可変性の一般的な構成を表現しているに過ぎないことを記しておく。 ― 新装版 マルチパラダイムデザイン p.123
C++言語関係等、参考リンクメモ
- テンプレート(WIkipedia)
- C++ プログラミングガイド 第 4 章 テンプレート
- http://docs.oracle.com/cd/E19957-01/805-7887/6j7dsdheo/index.html
- 継承
- http://www.s-cradle.com/developer/sophiaframework/tutorial/Cpp/inherit.html
- 6.8 仮想関数
- http://www.s-cradle.com/developer/sophiaframework/tutorial/Cpp/virtual.html
引用されているBrige パターンと同様のパターン
これは読んでいないですが一応リンクを貼ります。 C++ Idiom James O. Coplien
- http://tinf2.vub.ac.be/~dvermeir/c++/EuroPLoP98.html#EnvelopeLetter
- 振る舞いの継承
p.129
アプリケーションドメインを分析するときには、振る舞いの継承に焦点をあてる。もし、実装構造の共通性に焦点をあてたとすると、設計の柔軟性に関して妥協しなければならなくなる可能性がある。
どちらかというとアンチパターンとして書かれている、「実装構造の共通性に焦点を当てる」とは? → Numberの例で言うと、Complex と Integer をグループ化できないと捉えてしまうと、複雑になり管理しにくくなるということかなと。 Integer は、 Complex のように実部、虚部を持たず、「実装構造」が両者は異なるのだけれど、 同じ操作演算を持っていると見なすことができるので、共通性があると考える。
「設計の柔軟性?」 → 拡張が可能であること、すぐに変更ができること、理解しやすいこと
- 6.5 クラステンプレートの例 https://github.com/kumamidori/mpd_cpp_snippets/blob/master/chapter6/6_5_ClassTemplate_Stack.cpp
- 6.6 関数テンプレートの例 https://github.com/kumamidori/mpd_cpp_snippets/blob/master/chapter6/6_6_FunctionTemplate_Max.cpp
- 図6.5 例 https://github.com/kumamidori/mpd_cpp_snippets/blob/master/chapter6/Fig_6_5_Set.cpp これはよく分かりませんでした。
- 仮想関数の例 https://github.com/kumamidori/mpd_cpp_snippets/blob/master/chapter6/6_8_VirtualFunction_Shape.cpp
p.135
- 共変 co-variant
- 共通性/可変性分析という用語について(杉本さんのツイートメモ)
https://twitter.com/sugimoto_kei/status/579509029501726720
「共通性」「可変性」は、かなり限定された、やや特殊な意味で用いられているのに、
共通な要素とそれ以外を分けるという一般論と誤解される。
「枠組み/選択肢型構造分析」みたいな表現の方が内容に合っているかも(くどい?)。
p.139
ほとんどのStackは、図6.9と同様のデータ構造を持つ。この実装は十分に一般的である。スタックされる要素がデフォルトコンストラクタを持つことを強制しないし、ポリモーフィックなデータ型を扱うこともできる。
- C++標準ライブラリのstackは、テンプレート引数で委譲するコンテナを指定できる。デフォルト値では図6.10に近い構造(正確にはdeque)になり、明示すれば図6.9相当のstackにすることも可能。負の可変性をテンプレートの特殊化ではなく、デフォルト引数で表現している
- 実際には図6.10のような実装でも、図6.9に対して一般性を失うことは殆どない(デフォルトコンストラクタを強制しない云々は、データ構造というより実装に依存した問題)。
C++標準stackを簡略化した実装 https://ideone.com/SJTv1H
ソリューションドメイン分析の必要性
6.1 "反対側"のドメイン (p.123)
なぜソリューションドメインを研究する必要があるのだろうか?設計とは、問題構造からソリューション構造を導くアクティビティであるという視点をとるならば、問題を理解するだけでは十分ではない。つまり、ソリューションドメインも問題ドメインと同様に理解しなければならない。
ソリューションドメインの言語機能とモデリング
6.2 C++ソリューションドメイン:概要 (p.125)
これらの言語機能の1つ1つが、パラダイム、すなわち世界を組織化する1つの方法を特徴づける。C++のプログラムは、これらの言語機能によってコードとして形作られる。さらには、バンキングシステム、テレコム系システム、GUIなどといったアプリケーションドメインの抽象を我々が考えたり表現したりする方法も、これらの言語機能によって形作られることになる。
概念メタファーの観点からは、モデリングとはソリューションドメインで表現可能なメタファーによってアプリケーションドメインを理解する(モデル化する)ことといえる。アプリケーションドメインの概念の一側面をソリューションドメインで表現可能なメタファーによって明らかにするともいえるだろう。
テンプレートパラメータのデフォルト式
6.5 クラステンプレート 6.5.1 テンプレートの特殊化 (p.128)
template <class T, class SequenceClass = dequeue<T> >
class Stack {
...
}
これはどのような意味なのか?
振る舞いの継承と設計の柔軟性
6.7 継承 (p.129)
アプリケーションドメインを分析するときには、振る舞いの継承に焦点をあてる。もし、実装構造の共通性に焦点をあてたとすると、設計の柔軟性に関して妥協しなければならなくなる可能性がある。実装構造に基づいた設計は、関連するドメインに広く適用することが難しいかもしれない。元のアプリケーションドメインに属する新しいアプリケーションに対してさえも困難であるかもしれない。振る舞いに焦点をあてると、設計に柔軟性を持たせやすい。このような思想が、CRCカード[Beck1993]や責務駆動設計(responsibility-driven design)[Wirfs-Brock 1990]などの設計手法の核心に存在する。
コードの解説求む
6.7.1 2つのドメインを整合させる (pp.129-134)
2つの仮想関数のグルーピング方法
6.8 仮想関数 (p.135)
仮想関数のグルーピングには2つの方法がある。1つ目は、アプリケーションドメインの抽象データ型を実装するクラスの中に仮想関数をグルーピングする方法、2つ目は、異なるクラスの仮想関数を1つの継承階層へとグルーピングする方法である。
解説求む。
振る舞いと意味
6.8 仮想関数 (p.135)
要するに、仮想関数群は、外部から観測可能な振る舞い、すなわち意味を共有する。例えば、
Shape(形)クラス以下に階層を形成するクラス群は、すべてdraw(描画)メンバ関数を共有する。このようなメンバ関数はほかにも数多くある。「描画される」というのは、すべての「形」が持つ振る舞いである。
ポリモーフィズム
6.8 仮想関数 (pp.136-137)
ポリモーフィズム[CardelliWegner1985]とは、「多くの形態」を意味する。共通性と可変性という本書の視点に合わせて、これを次のように言い換えることも可能である。 「ポリモーフィズムは、関連するいくつかの形態に何か共通するものがあるがそれぞれの形態は別個であるということを意味する」
図6.8も参照のこと。
C++における負の可変性の表現方法
6.11 負の可変性 (p.138)
- テンプレートの特殊化
- デフォルト引数
- 間接参照(データメンバキャンセルのため)
- private継承(振る舞いのキャンセルのため)
- 共用体
#ifdef
テンプレートの特殊化
p.128
template <class T, class SequenceClass = dequeue<T> >
class Stack {
...
}
dequeはC++標準のコレクションの一種。 テンプレート引数で配列ベースのStackにするのか、dequeベースのスタックにするのか等、内部構造を選択できるようにしているのだと思います。
// おそらくこういう実装を想定。コンテナの管理はすべてSequenceClassに委譲している
template <class T, class SequenceClass = dequeue<T> >
class Stack {
public:
void push(T v) { imp.push_back(); }
T top() const { return imp.top(); }
void pop() { imp.pop_back(); }
private:
SuquenceClass imp;
}
// デフォルトでは以下と等価
template <class T>
class Stack {
public:
void push(T v) { imp.push_back(); }
T top() const { return imp.top(); }
void pop() { imp.pop_back(); }
private:
deque<T> imp;
}
SequenceClassに与える型は特定のインターフェースを継承している必要が無い、というのはtemplateの特徴でもあります(push_back(), pop_back(), top()という3つのメソッドが上記のシグネチャで呼び出せればどんなクラスでも良い)。 Wikipedia:ダック・タイピング
stack<int> s1; // dequeを使う(デフォルト)
stack<int, list<int> > s2; // listを使う(特殊なニーズも満たす:負の可変性)
stack<int, list<int, my_allocator<int> > > s3; // list自身をさらにカスタマイズ
stack<int, my_container<int> > s3; // stack内の要件さえ満たせば、自作のコンテナでも良い
この本では型・構造の可変性をテンプレート引数で表現するにとどまっていますが、さらに振る舞いの可変性にまで拡張させたものが本書以降に現れたポリシー・クラス (by Modern C++ Design)の考え方と理解しています
負の可変性はルールの例外
6.11 負の可変性 (p.138)
C++のそれぞれのパラダイムは、バリエーションのルールを持つと見なすことができる。「オブジェクトパラダイム」では、バリエーションのルールは次のようになる。「ファミリの構成員は、基底クラスの中に据えた共通性に対して、新たな関数とデータを追加することができる」。ほとんどのパラダイムにおいて、可変性は共通性を侵すことはない。しかし、負の可変性は共通性を侵すため、バリエーションのルールに違反する。つまり、負の可変性はルールの例外なのである。
負の可変性の反転
6.11 負の可変性 6.11.2 負の可変性とドメイン分割 (p.148)
前項の技法は小さな負の可変性に適用される。負の可変性が大きくなるにつれ、バリエーションとしての特徴がなくなってくる。それは共通性となり、取り残された反対側が今度は可変性となるのだ!大きな負の可変性は、しばしば正の可変性として扱われなければならない。
仮想関数とADT
6.8 仮想関数(p.135)
仮想関数によるグルーピングには2つの方法がある。1つ目は、アプリケーションドメインの抽象データ型を実装するクラスの中に仮想関数をグルーピングする方法、2つ目は、異なるクラスの仮想関数を1つの継承階層にグルーピングする方法である。
- 1つ目の方法 5.1.3(p.113)では、純粋仮想関数(実装を持たない仮想関数)を使ってComplexという抽象データ型を定義し、その派生クラスでoperator+=を実装していた。この場合、ADTのinterface-実装対が1つのグループとなっている。
- 2つ目の方法 仮想関数はADTに限ったものではなく、単にシグネチャと意味が共通したメソッドを継承階層にまとめるのに使っても良い。基底クラスは「純粋」仮想関数でなくともよく、デフォルトの実装を持っていても良いし、シグネチャも共変性を持つ範囲で一部変えることができる。アプリケーションドメインから抽出されたクラスだけでなく、デザインパターン等のソリューションドメイン側の実装テクニックにも使える、といった意味合いを言っている?
オブジェクト指向入門 第2版 原則・コンセプト 6.5.1 (p.184)より
定義:クラス クラスは(部分的であっても良い)実装を伴う抽象データ型である。したがって、クラスを得るためには、ADTを準備し、実装を決めなければならない。ADTは数学的な概念である。実装はそれのコンピュータ向けのバージョンである。しかしながら、この定義には部分的な実装であってもよいと書かれている。次の用語により、そのようなクラスを完全に実装されたクラスから区別できる。
定義:暫定クラスと有効クラス 完全に実装されているクラスを有効クラス(effective class)という。 部分的にしか実装されていないか、全く実装されていないクラスを暫定クラス(deferred class)という。 クラスは暫定クラスか有効クラスのいずれかである。有効クラスを得るためには、実装に関する詳細をすべて用意しなければならない。暫定クラスについては、特定の形式の実装を選びながら、実装のいくつかの側面を開かれたままにすることができる。「部分的」な実装のもっとも極端なケースでは、実装に関する決断を全くせずに済ませることができる。そのようなクラスは完全に暫定的なクラスで、ADTと等しい。
仮想関数によるグルーピングには2つの方法がある。1つ目は、アプリケーションドメインの抽象データ型を実装するクラスの中に仮想関数をグルーピングする方法、2つ目は、異なるクラスの仮想関数を1つの継承階層にグルーピングする方法である。
- 1つ目の方法 5.1.3(p.113)では、純粋仮想関数(実装を持たない仮想関数)を使ってComplexという抽象データ型を定義し、その派生クラスでoperator+=を実装していた。この場合、ADTのinterface-実装対が1つのグループとなっている。
- 2つ目の方法 仮想関数はADTに限ったものではなく、単にシグネチャと意味が共通したメソッドを継承階層にまとめるのに使っても良い。基底クラスは「純粋」仮想関数でなくともよく、デフォルトの実装を持っていても良いし、シグネチャも共変性を持つ範囲で一部変えることができる。アプリケーションドメインから抽出されたクラスだけでなく、デザインパターン等のソリューションドメイン側の実装テクニックにも使える、といった意味合いを言っている?
ありがとうございます。別々のクラス階層にいたとしても、それらに一致するシグネチャを抽出してインターフェイスとすることができるよ、ということですね。これは複数のドメインにまたがるようなドメイン(相対的な水平ドメイン)の抽出ともいえそうです。
分類階層と多重継承
6.12 ソリューションドメインの拡張要素 6.12.1 多重継承 (p.153)
多重継承の「正統な」使用法は分類階層の表現であり、オブジェクト指向設計との関わりが深い。ある型があるカテゴリに属するとき、その型はそのカテゴリのサブタイプとして表現されるべきだろう。
ワークフローエンジンWorkflowerの型ActivityInterfaceの例:

6.12 ソリューションドメインの拡張要素 6.12.1 多重継承 (p.155)
しかし、多重継承を使うかどうかの決定や、継承をvirtualにするかしないかについての決定は、ドメインのセマンティクスに深く関与し、数多くのトレードオフが生じる。このような理由から、マルチパラダイムデザインでは、多重継承問題の解決はドメインに関する経験知識に委ねている。
共通性/可変性分析とデザインパターン
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン (p.157)
パターンにはさまざまなものがあるが、共通性/可変性分析の範囲内に含まれるパターンも存在する。つまり、本書の対象としている領域を表現するパターンが存在するのである。このことは興味深く、注目に値するもので、もしかしたら驚くべき発見だと言ってもよいかもしれない。現在、人気を博しているデザインパターンの多くが、共通性と可変性の典型的な配置をほとんど超えないのである。ここから、設計手法というものは、共通性分析の領域に数多くのパターンを織り込んでいるものだと考えることができるだろう。つまり設計の性質を考えれば、個々のパターンを「覚えなくてはいけないもの」とか「適用しなくてはいけないもの」といった例外として考える理由はどこにもないのである(このことについては、前の節で多重継承を例にとって、それが真であるということを見てきた)。
パターンを設計の例外としてではなく、共通性/可変性分析を含む一般的な分析・設計の例として捉えるのがよいということだろう。
ホットスポットと可変パラメータ
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン (p.157)
Gammaたちは、『デザインパターン』の中で、彼らのデザインパターンをまとめた表を掲載している([Gamma1995]、原書p.30)が、そこで設計上の可変性を利用している。この章でもたびたび可変性の表を参照してきた。Wolfgang Preeは、このような変更部位をホットスポット(hot spot)と呼んでいる[Pree1995]。ホットスポットは、マルチパラダイムデザインで言えば、可変パラメータに類似した概念である。
ソリューションドメインとしてのパターンの力
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン プログラミング言語を超えるパターン (p.158)
プログラミング言語では直接表現できないようなものが、パターンでは表現できることもしばしばある。マルチパラダイムデザインでは解決策を言語機能レベルで打ち立てるのに対して、パターンはどのように課題を解くかを明示的に示し、また設計する上での洞察にも富む。優れたパターンは、そのパターンの読者に対して、何を行うべきかを指示し、その結果がどのようになるかを示唆する。
パターンのカテゴリ
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン プログラミング言語を超えるパターン (p.158)
特に根拠があるわけではないが、パターンは3つのカテゴリに分類することができるだろう。すなわち、フレームワークパターン、デザインパターン、イディオムである。
パターンのスペクトル
フレームワークパターン---デザインパターン---イディオム
フレームワークパターン
例:Client/Server
イディオム
パターンスペクトル上、フレームワークパターンと反対の極地にあるのがイディオムで、実装する際に使用するプログラミング言語要素に密接に関係している。
デザインパターン
デザインパターンは中間にあり、スペクトルのその領域では、解構造によりアプリケーションンの構造を構築するということを行うのである。したがって、マルチパラダイムデザインが対象とするのと同じ変換を扱うことになる。
パターンとマルチパラダイムデザイン
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン パターンとマルチパラダイムデザイン (pp.158-159)
パターンとマルチパラダイムデザインの比較
| パターン | マルチパラダイムデザイン | |
|---|---|---|
| 解決の方針 | ドメインに固有の、繰り返し登場する課題を解く | 過去の解決策に依存しない |
| 対象ドメイン | アプリケーションドメインおよびソリューションドメイン | ドメインに依存しない |
| ドメイン間の関係 | ドメイン間の対応づけは扱わない | アプリケーションドメインとソリューションドメインを1つの構造に収束させる |
| 解決策の汎用性 | かなりの数の解決策をさまざまな方法で組み合わせる | プログラミング言語の小さな組を使って解決策を構築する |
ある意味で言えば、マルチパラダイムデザインは、パターンのどのような集合を取り出してみても到達し得ないほどに汎用的である。マルチパラダイムデザインと同程度の汎用性を持たせるためには、パターンカタログが膨大なものでなければならないだろうが、そうなると今度は、必要なパターンの検索が容易にはできなくなるかもしれない。
パターンとマルチパラダイムデザインの関係
それでは、パターンとマルチパラダイムデザインはどのように関係するのだろうか。パターンがどのように利用されるのかという立場から、3つの視点に立脚してこの関係を見ることができる。
- ソリューションドメイン分析に登場する共通性/可変性対の別名として。
- C++の言語構成要素よりも抽象度が高い抽象として。C++の言語構成要素はこれらの抽象を下支えしている。
- 負の可変性を扱う強力なメカニズムとして。
マルチパラダイムデザインで作るドメインモデルの構造
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン パターンとマルチパラダイムデザイン (p.159)
マルチパラダイムデザインは、アプリケーションドメインとソリューションドメインを互いに織り合わせて、1つの構造に収束させる。この構造は、それぞれのドメインのフォースを調和させたものである。
ドメイン駆動設計のモデル駆動設計に近いが、ドメイン駆動設計に比べるとソリューションドメインがアプリケーションドメインと同等かそれ以上の重要性を持つのがポイントだろう。
ここでのフォースは、p.158で述べられている意味だろう。
優れたパターンの多くでは、トレードオフについても議論されるのが一般的で、これはフォース(force)と呼ばれている。
パターンの役割
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン ソリューションドメインの構成要素に対する別名 (pp.159-160)
共通性と可変性は、大部分の設計手法やツールの要となるものだ。共通性と可変性に関して繰り返されるパターンは、本書でパラダイムと呼んでいる設計スタイルを支援できるように、その要をプログラミング言語に置くことが多い。その共通性と可変性の組み合わせの中には、プログラミング言語で実現されるのが望ましいといえるほどには一般的だと考えられないものもある。しかし、たとえそうだとしても、設計の語彙にできるほどには一般的である。このような要素がパターンとなり、アプリケーションドメインとソリューションドメインに存在する抽象を補うことになる。
パターンについてジェネレーティブプログラミングには以下のような記述がある。
抽象度が高まっていけば、パターンとイディオムはたった1つのフレーズを表すといえます。この抽象度は、言語機能の中で高まっていくものかもしれません。一度、あるデザインパターンやイディオムが言語機能になれば、それはパターンとしての骨子を失わせます。それが、これまでの感覚におけるデザインパターンやイディオムであるとは、もはや考えられないでしょう。ちょうど、継承がデザインパターンであるとは考えられないようにです。その一方で、デザインパターンを記述する書式は、ある固有な問題を解決するための言語機能の適用性を記述するために、有用なままであるでしょう。 ― ジェネレーティブプログラミング (IT Architects’Archive CLASSIC MODER) p.274 第8章 アスペクト指向プログラミング
ソリューションドメインの拡張
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン (p.161)
表6.2にC++のソリューションドメイン構造をまとめた。これにC++のソリューションドメインに対して「パターンソリューションドメイン」を追加すると、共通性と可変性の豊富な組み合わせを表現でき、さらに、C++という言語のみから生じる表現を超えた要素を用いるソリューションドメインが支援されることになる。これをまとめたのが表6.3である。このテーブルはC++ソリューションドメインのテーブルの拡張だと見なすことができる。変換分析(7.2節)を行う際には、この2つのテーブルを1個のものと考えればよい。
プログラミング言語に加えて、フレームワーク・ライブラリ、パターン、モデリング技法(例:リレーショナルモデリング)等でソリューションドメインを拡張でき、それらを1つのドメインと見なすことができる。
構造の共通性のない共通化
6.12 ソリューションドメインの拡張要素 6.12.2 デザインパターン (p.162)
Bridgeを考えるとき、ファミリ構成員の構造には共通性はないと仮定することもできる。そう考えると、各ファミリ構成員は空の土台の上に独自の構造を持つことになる。すなわち、ファミリ構成員は互いに完全に無関係の構造を有し、負の可変性については考慮しなくてよい。このとき共通性は、ファミリ構成員と関係するシグネチャ(基底クラスのインターフェイス)という、さらに広いコンテキストから生じることになる。
https://github.com/phpmentors-jp/mpdosaka/issues/6#issuecomment-104823669 も参照のこと。