DCIアーキテクチャ - Trygve Reenskaug and James O. Coplien

この記事はartima developerに掲載されている、Trygve Reenskaug氏とJames O. Coplien氏による記事「The DCI Architecture: A New Vision of Object-Oriented Programming」を、著作権者であるBill Bennrs氏の許可を得て翻訳したものです。本文内の図の著作権はArtima, Inc.に帰属します。(原文公開日:2009年3月20日




要約
オブジェクト指向プログラミングはプログラマとエンドユーザの視点をコンピュータコードにおいて統一するものと考えられていた。この恩恵はユーザビリティとプログラムの分かりやすさの両面にわたる。しかし、オブジェクトは構造をとらえるのに長けている一方で、システムの動作をとらえることができていない。DCIはエンドユーザのロールに関する認識モデルとロール間の関係をとらえるための構想である。

オブジェクトはまず人々とそのメンタルモデルに関するものであり、ポリモルフィズムや結合、凝集に関するものではない

オブジェクト指向プログラミングは、コンピュータは人間の精神を拡張したものであるというDoug Englebartの構想から生じた。Alan KayDynabook構想1はしばしば近代におけるラップトップPCの先駆と目されているものだが、これはこのような構想の典型だと言えよう。すなわち、仲間とも呼びうるような、自己の拡張としての本当に「パーソナル」なコンピュータという構想である。Kayは後にこの構想をソースコードにまで持ち込むために、言語を作っている。それがSmalltalkである。事実、オブジェクト指向プログラミングにおける先駆者たちの目的は、エンドユーザのメンタルモデルをコードにおいてとらえることだった。これらの構想が今日のわれわれに残してくれたのは、インタラクティブでグラフィカルなユーザインタフェースと、世の中のプログラミングにおけるオブジェクト指向言語の隆盛である。


GUIに取り組むとき、ユーザは2つのことを行っている。すなわち、考えること実行することである。人間と機械の間でスムーズなインタラクションを行うためには、コンピュータの「メンタル」モデル(これは同時にプログラマのメンタルモデルでもある)とエンドユーザのメンタルモデルがマインドメルド的なやり方で平行していなければならない。最終的には、ユーザがインタフェース側で行うどんな処理も、コード内のオブジェクトを操作することになる。ユーザによる操作がプログラムの状態にどう影響を与えるのかということについて、プログラムが正確なフィードバックをリアルタイムに提供していれば、ユーザによるエラーや予想外の動きは減る。優れたGUIはこのようなサービスを提供する。インタラクティブなプログラムを用いるということは、医者がプローブを患者の気管支内に通すようなものだ。プログラムメモリ上のオブジェクトを見ることができないのと同じように、患者の体内にある実際のプローブを見ることはできない。プログラムとのインタラクションをガイドするためには、プログラムの構造(もしくは、気管支プローブ)に対応する外的な表象が何か必要になる。

構造のマインドメルドはわれわれの得意分野である

オブジェクト指向デザインとモデル-ビュー-コントローラ (MVC) フレームワークはいずれもこの構想を支えるために発達した。MVCの目標はエンドユーザの脳とコンピュータの「脳」(つまりメモリとプロセッサ)が直接結ばれているという幻想を提供することだった。


いくつかのインタフェースでは、この対応は明らかである。パワーポイントのスライド上に円を描けば、頭の中にある円がこのコンピュータメモリ上の表象と直接紐づけられるし、スプレッドシート型台帳の行や列は、スプレッドシートプログラムの画面に表示される行や列に紐づけられる。また、テキストエディタのページ上の文章は記述されたドキュメントというわれわれのモデルと、保存されたテキストというコンピュータのモデルの両方を反映している。構造化へ向けたオブジェクト型アプローチによってこのような整合が可能になり、人間の思考がすみやかにコンピュータの構造の概念と整合する。

MVCは人間とそのメンタルモデルに関するものであり、オブザーバパターンに関するものではない。

ほとんどのプログラマMVCのことをオブザーバパターンのいくつかのインスタンスをうまく組み合わせたものだと考えているし、ほとんどのプログラミング環境ではMVCの基底クラスが提供されており、それらのクラスはモデル、ビュー、コントローラの状態と同期化するために拡張することもできる(モデル、ビュー、コントローラは、実はユーザにより提供されるオブジェクトによって演じられるロールなのだが、ロールについては後に記述する)。だからといって、MVCは単にハウスキーピングのためのテクニックなのだと言えるだろうか?MVCについてこのように考えることは、コンピュータマニアの視点をとるということだ。このような視点を「モデル-ビュー-コントローラ」と呼ぼう。だがより深い視点から見れば、このフレームワークはユーザインタラクションから情報の表象を分離するために存在している。この可能性において、われわれはこのフレームワークを「モデル-ビュー-コントローラ-ユーザ」と呼ぶ。これは、4つの重要なアクタをすべてとらえているもので、略してMVC-Uとする。


これはこの後で付け加えられる用語をより正確に定義するのに役立つ。MVC-Uはコンピュータのデータとエンドユーザの頭の中にあるものとのつながりを作るためのものだ。データは情報の表象である。コンピュータ内では情報はビットとして表象される。しかしビットはそれ自体では何も意味せず、何かを意味するのはユーザの頭の中でだけであり、それも両者の間にインタラクションがあったときだけだ。エンドユーザの頭がそれらのデータを解釈できるのであり、そうすることでデータは情報になるのだ。情報とは解釈されたデータを指すのにわれわれが用いる用語である。情報はエンドユーザのメンタルモデル2における主要な要素である。


この紐づけはまずエンドユーザがインタラクティブなインタフェースに取り組むという形で行われる。ユーザはインタフェースを、それを描く元となるデータとビジネスの世界についてのモデルとの間に経路を作るために使用する。適切に設計されたプログラムは、データモデルにおいてうまく情報モデルをとらえるし、少なくてもそのようにしているという幻想を与える。そういうことをソフトウェアができるならば、ユーザはコンピュータのメモリが自分の記憶の延長であるように感じる。そうでなければ、このミスマッチを埋めるために「変換」プロセスが必要になる。この変換をコードで行うならば、せいぜいぎこちないものになるだけだ(そしてコーダがエンドユーザの認識モデルを知っていれば、そうなるとも限らない)。しかし、ユーザがこの紐づけをリアルタイムに頭の中でやらなければいけないとしたら、これは苦痛を伴い、ぎこちなく、混乱を招き、間違いのもととなる。これら2つのモデルを統合することは直接操作メタファ("direct manipulation metaphor")と呼ばれる。これは、エンドユーザが頭の中にあるイメージを反映しているオブジェクトをメモリ上で実際に操作しているという意味だ。




図1. 直接操作

直接操作メタファ

われわれがシステムに対して持つ要求は、情報からそれをプログラムにおいて表象するデータへ向けた短い経路を提供するというものだ(図1)。モデルの仕事は生のデータを「フィルタリング」し、プログラマがそれらについてシンプルな認識モデルの観点から考えられるようにするというものだ。例えば、電話システムの根底には、ローカルな電話通信の基本構成要素を表象するオブジェクトがあるかもしれない。この構成要素は、ハーフコール("half-calls")と呼ばれるものだ。(考えてみて欲しい:単に「電話をかけ」たとした場合に、別の町にある2つのコールセンター間で通信していたら、どこに「コール」オブジェクトはあるべきなのか?「ハーフコール」の概念はこの問題を解決する。)しかし、電話交換手は「コール」を物だと考える。これには存続期間があり、その生存期間を通じて接続してくるパーティの数によって伸び縮みするものなのだ。この幻想はモデルによって支えられる。コンピュータのインタフェースを使うことで、エンドユーザは「コール」システム内にある実際の物を直接操作しているかのように感じる。モデルが変われば(ハーフコールについての)同じデータを違うやり方で表すかもしれない。これはまったく別のエンドユーザが持つ視点に役立てるためだ。この直接操作という幻想は、コンピュータがどのようなもので、どう人の役に立つかということをオブジェクト的視点から見た時、その中核をなすものだ。


ビューはモデルを画面上に表示する。ビューが提供するのは、モデルと情報をやりとりするためのシンプルなプロトコルだ。ビューオブジェクトの中核は、エンドユーザの関心に応じてモデルのデータを表すというものだ。ビューが異なっても、同じデータ(つまり、同じモデル)を、全く別のやり方で表示していることがある。古典的な例には、あるビューは棒グラフでデータを表示しているのに対し、他のビューは同じデータを円グラフで表示するというものがある。


コントローラはビューを生成し、ビューとモデルの間をとりもつ。コントローラが通常果たす役割は入力されたユーザの行為を解釈するということだ。ユーザの行為を受け取る形には、打鍵やロケータデバイスのデータ、あるいは他のイベントがある。




図2. モデル-ビュー-コントローラ-ユーザ


これら3つのロールは一緒になって、それらを演じるオブジェクト間のインタラクションを定義する。これらはどれも、コンピュータのメモリがエンドユーザの記憶の延長であるという幻想を支えるという目的を持っている。モデル-ビュー-コントローラ-ユーザを要約すれば、コンピュータと人間のインタラクションにおける思考("thinking")の部分を支えるには優れているのだ。

・・・しかし構造をとらえるかわりに、オブジェクト指向はふるまいをとらえられていない

残念ながら、オブジェクト指向ではわれわれが行動する("doing")ことについてどう論理的に考えているかについては、うまくとらえることができてこなかった。インタラクションが見いだされるべき明確な「場所」はGUI上にもコード内にも存在しない。これにはいくつかの例外がある。オブジェクトを1つしか持たないシンプルなアクションの場合には特にそうだ。例えば、優れたインタフェースがあれば、画面に表示された円の色を変えるために、適切に配置されたペイントブラシを使用することができる。プログラム内では、円の色を変えるコードはそれ自体が円オブジェクトの一部である。このようなシンプルなケースでは、エンドユーザのメンタルモデル、コード、画面はすべて整合性がとれている。しかし、スプレッドシートにおいては、カラムの合計を見ることはできない。その代わりに必要になるのは、いくつか不思議な呪文を唱えて、以前に作成した公式を再表示するサブウィンドウなり別のフィールドを表示させるということだ。適切な画面設計とインタラクション設計をすることで、エンドユーザに対するダメージを限定することはできるし、このようなアクションを可視化している驚くほど優れたインタフェースもいくつかある。しかし、このようなインタフェースは、神秘に包まれていることの方がはるかに多いのだ。有名なワープロソフトで図を段落に挿入したり、あるいはその逆を行おうと苦労する時に求められる、完全に不可思議な儀式を思い起こしてほしい。


エンドユーザにとってはまだどうにかなるとしても、プログラマにとっては、それと同じか、さらにひどい事態になっている。プログラマもまた人間なのであり、ユーザの要求に対する理解とコードに対する理解を紐づけることができていて欲しいのだ。オブジェクト指向プログラミング言語は伝統的に、オブジェクト間のコラボレーションをとらえる方法を何も提供してこなかった。オブジェクト指向プログラミング言語は、このようなコラボレーションの間を流れているアルゴリズムをとらえないのだ。オブジェクトのインスタンスによってとらえられるドメインの構造と同じように、コラボレーションとインタラクションも構造を持っている。これらもまたエンドユーザのメンタルモデルの一部を形成しているにも関わらず、その凝集された表象はコード内には見つけられないのだ。例を挙げよう。ユーザはワープロソフトでスペルチェッカとインタラクションする時にいくつか期待するものがあり、同時にスペルチェッカによるテキスト、辞書やエンドユーザとのインタラクションについての考えをあらかじめ持っている。しかし、ワープロ内のどのオブジェクトがスペルチェック処理をカプセル化するべきなのだろうか。編集バッファ?辞書?グローバルなスペルチェッカオブジェクト?これらの選択肢のいくつかは、スペルチェックを行うオブジェクトの凝集度を弱めるし、他の選択肢はオブジェクト間の結合度を強めてしまうことになる。


この記事でわれわれが示すのは、ロール、アルゴリズム、オブジェクトをどのように結びつけるか、そしてコードとエンドユーザのメンタルモデルの紐づけをより強くするためにこれらをどう関連づけるかについてである。この結果としてあらわれるアーキテクチャが基づくのは、オブジェクトのデータ(D)、オブジェクト間のコラボレーション(C)、そしてユースケースシナリオがロール間のインタラクション(I)を包含する方法である。これが、DCIアーキテクチャである。

どこで道を間違えたのか?

エンドユーザのメンタルモデルのうち、ある種のオブジェクトに対して行う行動をとらえることができないということについておおよそ見てきた。これは1980年代から1990年代の前半にかけて流行した神話である。このような考え方から生み出されたバズワードに、擬人化デザイン("anthropomorphic design")、スマートオブジェクト("smart objects")、システムのエマージェント挙動("emergent system behavior")がある。システムのふるまいは、ローカルメソッドが数十、数百、数千とインタラクションすることから「浮かび上がる」("emerge")べきものなのだとわれわれは教えられた。当時流行した言葉は次のようなものだ。「ローカルに考えよ。グローバルなふるまいは気にしなくても大丈夫。」プロシージャのようなメソッドを書いていたり、プロシージャ的な分割を行っているのを見られたら、オブジェクト指向コミュニティから「分かっていない」と遠ざけられた。


実際、GUIが引き起こす問題のほとんどは、プログラマがエンドユーザの認識モデルをコードでとらえることができていないことに起因している。MVCフレームワークにより、ユーザはシステムがどのようなものであるかについて論理的に考えることができるようになった。これはユーザの認識モデルのうち、思考の部分である。しかし、オブジェクト指向にほとんどなく、MVCにおいては全く存在しないことがある。それは行動をコードでとらえることができるよう開発者を助けるということだ。エンドユーザのふるまい上の要件について論理的に考える場所を、開発者は持っていないのである。


1960年代には、われわれはふるまいの要件をプログラムと、それを実装したFORTRANコードに持ち込むことができたし、それらを大きな赤ペンと一緒に職場の仲間に渡し、コードが要件と合致しているかどうかをレビューしてもらうこともできた。コードの形式はすべて要件の形式を反映していた。1967年に、ソフトウェア工学によってこれができなくなってしまった。アルゴリズムはオブジェクト間に分散しなければなくなったのだ。すべてのアルゴリズムを表象するような大きなメソッドは、「純粋な」オブジェクト指向設計ではないと信じられたからだ。それでは、どうやってアルゴリズムを分割し、オブジェクトの中に分散させたのだろうか?それは、結合と凝集の基本に基づいて行われた。アルゴリズム(メソッド)はそのアルゴリズムともっとも親和性の高いオブジェクトと共に用いられねばならない。これが凝集性の最適化である。


あるアルゴリズムが1つのオブジェクトの中に見いだせる場合には、これはうまくいった。例えば、画面上に表示される円の色を変えたり、ワープロソフトのテキストバッファに入力された文字を付け加えたりといった場合である。しかし、業務機能の中でも興味深いものは、複数のオブジェクトに及ぶことが多い。テキストエディタ内のスペルチェッカには、画面、メニュー、テキストバッファが含まれる。図のエディタにとってすら、重なる領域を計算するという問題は複数のオブジェクトに属するのだ。オブジェクト指向によってわれわれが押し込められた世界は、アルゴリズムを分割し、いくつかのオブジェクト間に分散させ、できる限り断片的に仕事をするというものだった。

ユーザの頭に戻る

オブジェクト指向の目標がエンドユーザが自身の世界について持つ概念モデルをとらえることであったのなら、そこに何があるかを見つけるためにユーザの世界へ立ち戻ることが有効だろう。なじみのある領域から始めよう。データモデルは、多くのマニアが今日オブジェクトと呼んでいるものである(厄介なことにここでもモデルが出てくるので、議論をクラスに限定する)。ここからはじめて、よりダイナミックな概念であるロールコラボレーションへと移っていこう。これら3つ、すなわちデータモデル、ロールモデル、コラボレーションモデルはプログラミング言語から独立した概念的な関心である。しかし、もちろん、我々の目標の1つには、プログラミング言語がこれらのものを表現しなければならないというものがある。したがって、これらの概念をコードにおいて表現するプログラム上の概念にも目を向ける。これらの概念の1つはクラスと呼ばれ、もう1つがロールと呼ばれる。

データ:世の中に対するユーザのメンタルモデルを表象する

データの管理はコンピュータサイエンスにおいて2番目に古い仕事である(最も古い仕事については後述する)。古いデータフロー図(DFD)を用いる人々は、データが設計における不変の箇所であると言ってきた。この真理はオブジェクトに持ち込まれ、オブジェクト設計者たちは安定したオブジェクト構造を探すように仕向けられた。


初期のオブジェクト指向設計におけるとりわけ単純なルールに、名詞(例えば要件ドキュメント内にあるもの)がオブジェクトであり、動詞がメソッドであるというものがある。この二分法はプログラミング言語が表現できる2つの概念に対して自然にフィットする。オブジェクト指向プログラミング言語は、純粋なものは特に、すべてのものをオブジェクトか、その中にあるメソッドで表現した。(もちろん、ほとんどのプログラミング言語はこのためにクラスを使用した。重要なのはオブジェクトフレームワークの外部には何の存在も想定されていないということだ)。預金口座オブジェクトを見たとして、それがオブジェクトであるという事実は、預金口座をそのようなもの(あるいはクラス)としてとらえることにつながる。残高を減らすことも、引き出しをすることもできるという事実は、メソッドとしてまとめられる。これはどちらもふるまいである。しかし、これら2つのふるまいは劇的に異なるものだ。残高を減らすということは単にデータの特質にすぎない。つまり、データが何かということだ。引き出しをするということはデータの目的を反映している。つまり、データが何をするかということだ。引き出しを制御するということが暗示しているのは、トランザクションの動作、ユーザとのインタラクション、リカバリ、エラー条件の処理、業務ルールなどだが、これらはどう考えたとしてもデータモデルの考え方をはるかに凌駕している。実は引き出しはシステムのふるまいであり、システムの状態を必要とする。それに対して残高を減らすということが関係するのはオブジェクトの状態だけだ。これら2つの特性は、システムのアーキテクチャ、ソフトウェアエンジニアリング、変更に対する保守の割合などの重要な視点から見ても全く種類が異なるものなのだ。オブジェクト指向はこれらを同じくくりにまとめてしまうのである。


この手法が持つ問題は次のようなものだ。もしオブジェクトが安定したもので、あらゆるコードがオブジェクトの中に納められているのであれば、変化する部分をどこに表象させればよいのだろうか?良いプログラムについて長く言われている主要な特質に、保守のしやすさの観点から、安定したものと変化するものを分離するというものがある。オブジェクトがコードの安定した場所に対応するならば、要件の変化をコード上で表現するのにオブジェクトとは異なる機構が存在しなければならない。これはアジャイルが持つ進化と保守性という構想を支えるものでもある。しかし、オブジェクトは安定したものであり、オブジェクト指向プログラムにおいて「他の機構」は存在しないのだ。


このような作為的な制約に忠実であったため、オブジェクトの世界は作為的な解決法に行き着いた。それが「差分プログラミング」ないし「拡張によるプログラミング」を表現するために継承を用いるというものだ。継承がもっともよく理解されているのは、おそらくドメインモデルにおけるオブジェクトを類型化する方法としてである。たとえばファイルに対する排他的アクセスファイルはディスクファイルの特殊な種類だろうし、磁気センサ、工学センサ、機械センサはより一般的なセンサという考え方の異なる実装であろう(これは継承ではなく、サブタイプだという反論があるかもしれない。しかしこれら2つの意図を区別して表現するプログラミング言語はきわめて少ない)。継承が基底クラスのバリエーションを表現できたため、これはすぐに「安定した」基底クラスに対するふるまいの追加をとらえるための機構となった。事実、この手法はすばらしい設計技法だともてはやされ、オープン-クローズ原則と呼ばれた。クラスは修正に対して閉じていなければならないが(つまり、ドメインモデルの安定性をとらえるために安定し続けなければならないということ)、拡張に対しては開かれていなければならないというものだ(拡張とは、新しいユーザのふるまいを支えるために、予期しなかった新しいコードを追加するということ)。このような継承の用法はプログラミング言語の世界からはい出し、設計における専門用語となった。


この道のどこかで、静的な型付け言語がソフトウェアエンジニアリングに支えられて優位に立った。静的型システム分析における重要な局面がクラスである。この構成概念により、コンパイラがメソッドの探索とポリモルフィズムのために効率的なコードを作り出せるようになったのだ。Smalltalkが最初に持っていたオブジェクトとダイナミックな実行環境に関する構想は本当に先見の明があるものだったが、そのSmalltalkすらクラスという妥協の犠牲者となった。クラスは、オブジェクトとよばれる分析概念の実装ツールとなった。この動から静への転換は、ダイナミックなふるまいがとらえられなくなることの始まりだった。


特にSmalltalkC++において、継承はサブタイピングを表現するための一般的な方法になっていった。Smalltalkにおいてはサブタイプを偽装することも可能で、オブジェクトの継承階層内にあるどのクラスにおいても特定のメソッドを呼び出すことができる。その際にデフォルトとなる実装が基底クラスにあるかどうかは関係しない。これは役に立つかもしれないが、呼び出されているメソッドを見つける時に起きる問題を深刻なものとした。基底クラスのインタフェースがオブジェクトの全ふるまいを表象していないからだ。静的型付け言語は、継承グラフを基底クラスのインタフェースによって完全に表象される独立した抽象化デザインとして作るという文化を生み出した。しかし、継承によるプログラミングは階層の一番下で行われるため、新しく付け加えられるメソッドは基底クラスに現れず、さらにまずいことに基底クラスに追加される必要があった(例えば、C++における純粋な仮想関数として)。これが、新しいメソッドを組み込むために継承階層を拡張する度に起きるのだ。


別の選択肢は静的型付けの恩恵を頼み、サブクラスの利用者が拡張によるプログラミングによって追加されたクラス定義にアクセスするようにしむけるというものだ。これにより、基底クラスの「完全性」("integrity")は保たれる。しかし、これが同時に意味しているのは、静的型付け言語がクラス階層の中に埋もれたレイヤ間のクロスリンクを助長するということだ。これはひっそりとカプセル化が破られていることを意味する。この結果の1つがグローバルヘッダファイルの蔓延である。C++の世界は実行時型情報(RTTI)やその他多くの技法を用いて対応することで、この問題を管理するのに役立てようとした。一方で動的型言語のコミュニティは、自分たちにとってそんなことは問題でないと肩をすくめていた。


オブジェクトコミュニティのレトリックは1980年代の中頃に継承を避け始めた。しかし、それはいくつかの恐ろしい話によって何となく加速されたものである(25段の継承階層などといった具合に)。結果として生まれたのは、ビジネスのふるまいをコードの中から追跡しなければならないという悪夢である。


結論として、このひどいストーリーが示唆しているのは、派生による継承がとうてい理想的とは言えない解決方法であるということだ。しかし、実は、継承がすべてを台無しにしているという訳ではない。このようなコードの変更はふるまいに関する要件の変更に由来するものだし、このような要件の変更のほとんどはエンドユーザがコードに対して新しいふるまいを求めることに端を発している。結局ソフトウェアというものはサービスなのであり、実は製品ではないのだ。そしてソフトウェアが力を持つのは、タスクとタスクにおける成長と変化をとらえることができる能力があるという点なのだ。このことが特に信憑性を持つのは、(もう何年も行われている)議論、すなわちデータモデルは時間が経っても比較的安定しているということに照らし合わせた時である。アルゴリズムの構造とドメインの構造の間にある不一致は、クラスが成長の単位としてはまったく向いていないことを示すものだろう。これについてはまた後述する。


このような見方から得られる重要な教訓がもう1つある。それはドメインクラスはデータの処理を行ってはいけないということだ。基本的なドメインオブジェクトは、ドメインエンティティのエッセンスについてわれわれが持つ根本的な考え方を表象している。積み上げ続けられたユースケースとして伝統的なオブジェクト指向開発に負荷をかけてきた、大量の処理プロセスやアルゴリズムではないのだ。預金口座オブジェクトは何ができるかという問いに対して、次のように答えることはできるだろう。残高を増やしたり減らしたりすることができ、残高を表示することができる、と。しかし、ここで預金を扱うことができる、と答えたとすると、突然ATMの画面や監査証跡を用いたトランザクションとインタラクションの世界に投げ込まれることになる。今やオブジェクトの外に飛び出し、預金口座単独では知り得ない多くのビジネスロジックとの結合について語っているのだ。オブジェクトに最初からビジネスの知識を与えたいと思ったとしても、インタフェースは時間が経ってもそれほど変わらないからなんらか正しいものが作れるという自信は、最初のユースケースがシステムが続く限り存在するコードのごく一部しか与えてくれないという事実によって打ち砕かれる。つまり、シンプルで安定したデータモデルは、ダイナミックなふるまいのモデルから切り離さなければならないのだ。

ロール:同じくユーザの頭の中にあるアクションという新しい概念(実は、新しいこともないが)

それではユーザの頭の中に戻り、すべてがオブジェクトであるという通常のステレオタイプを再考しよう。ここでわれわれが、ATMを構築しようとしており、実現したいユースケースの1つが振替であるとしよう。さいごに口座振替を行った楽しい記憶について質問をされたら、どのように答えるだろうか?このような質問に対する典型的な回答は以下のようなものだ。「そうですね、まず振り込み元になる口座と金額を選択し、振り込み先口座を選択します。」一般的には、これよりももう少しあいまいな表現になり、「振り込み元」や「振り込み先」の代わりに、「ある口座」「別の口座」という単語が使われる。


「まず預金口座を選択し、次に金額、最後に投資口座を選択します」と言う人がほとんどいないことは注目に値する。そのように答える人もある程度はいるだろうが、このようなレベルに行くことは問題を作為的に制約することになる。このようなシナリオが見られるどんな2つのクラスも、2つの口座クラスを法として合同なのだ。実はわれわれは皆、頭の中に資金の振替に対する一般的なモデルを持っているのであり、これはそこに含まれる口座がどんなタイプであるのかということに依存しない。このようなモデル、つまりインタラクションこそ、ユーザの頭からコードへと写し取りたいと考えているものなのである。


したがって、最初に導入する新しい概念はロールである。オブジェクトがそれが何であるのかをとらえるのに対して、ロールはオブジェクトが何をするのかという一連のふるまいをとらえる。実は、ロールという概念はなじんだものではないが、新しいものでもない。ロールベースのモデリングは少なくともOORAM手法にまで遡る。これは1996年に出版されたものである3。ロールになじみがないのは、われわれの(すくなくともマニアの)オブジェクト的思考はわれわれのプログラム言語に由来し、そういった言語はロールを表現する能力が貧弱だったからだ。


ロール間を通り抜けるインタラクションもプログラミングにとって新しいものではない。これはアルゴリズムと呼ばれるものであり、そしておそらく語彙と一般規則を持つものとしてはデータに先立つ唯一の設計形式である。興味深いのは、われわれが意識的にロールを通じてアルゴリズムを通り抜けているということだ。これはあたかも、使い古された手続き的分割("procedual decomposition")を用いてアルゴリズムをブレイクダウンし、分割結果をロールの境界に従って配置させているかのようである。同じことは伝統的なオブジェクトモデリングでも行っているが、違うのは手続き的分割(メソッド)をオブジェクトの境界に従って配置させている点である。


残念なことに、オブジェクト境界は既に別のものを意味している。この境界はカプセル化されたドメインに対する知識の中心、それもデータに対する知識の中心なのである。認識に合ったまとまりへとアルゴリズムを段階的に洗練することがデータモデルによって作られる境界と整合することの必然性を示す証拠はほとんどない。旧態依然としたオブジェクト指向はデータとアルゴリズム両方の境界づけのために同じ機構を用いることを強いるのであり、この機構はクラスと呼ばれている。この場合どちらかの境界づけが勝利を収めると思われる。アルゴリズムの境界が勝てば、アルゴリズムの断片が1つのオブジェクトに書かれるが、他の人への説明は必要となり、結合度は強くなってしまう。データの境界が勝てば、アルゴリズムはそれが割り当てられるオブジェクトの主題にとって適切になるよう断片的に切り分けられることになり、凝集度の低い、きわめて小さいメソッドができることになる。旧態依然としたオブジェクト指向は、とりわけこのような適切な粒度("fine-grain")のメソッドを作るよう推奨する。例えば、典型的なSmalltalkのメソッドは3行なのである。


ロールは一連の操作を実行する上での自然な境界を提供する。これはユーザが論理的に相互を関連づけているものだ。振替を例に振り込み元口座と振り込み先口座のロールを見てみると、アルゴリズムは次のようになるだろう。

  1. 口座所有者はある口座から別の口座への振替を選択する。
  2. システムは妥当な口座を表示する
  3. ユーザは振り込み元口座を選択する
  4. システムは残りの妥当な口座を表示する
  5. 口座所有者は振り込み先口座を選択する
  6. システムは金額をたずねる
  7. 口座所有者は金額を入力する
  8. 振り込まれる資金を移動し、決算する

資金の移動と決算についてのユースケースは以下のようになる。

  1. システムは資金が取得可能であることを確認する
  2. システムは口座を更新する
  3. システムは収支情報を更新する

設計者の仕事は、このユースケースを、トランザクションのような設計上の問題を支えられるアルゴリズムに変換することである。そのアルゴリズムは次のようになるだろう。

  1. 振り込み元口座はトランザクションを開始する
  2. 振り込み元口座は資金が取得可能であることを確認する(預金引き出しに割り込まれないように、このチェックはトランザクション内でやる必要がある!)
  3. 振り込み元口座は自身の残高を減らす
  4. 振り込み元口座は振り込み先口座に残高を増やすよう要求する
  5. 振り込み元口座はこれが振替であることを記録してログを更新する(つまり、単なる引き出しではないということ)
  6. 振り込み元口座は振り込み先口座にログを更新するよう要求する
  7. 振り込み元口座はトランザクションを終了する
  8. 振り込み元口座は口座所有者に振替が成功したことを伝える

このアルゴリズムに対するコードは以下のようになる

template <class ConcreteAccountType>
class TransferMoneySourceAccount: public MoneySource
{
private:
 ConcreteDerived *const self() {
    return static_cast<ConcreteDerived*>(this);
 }
 void transferTo(Currency amount) {
    // このコードはレビューが可能であり
    // スタブを用いて意味のあるテストもできる
    beginTransaction();
    if (self()->availableBalance() < amount) {
      endTransaction();
      throw InsufficientFunds();
    } else {
      self()->decreaseBalance(amount);
      recipient()->increaseBalance (amount);
      self()->updateLog("振替 出金", DateTime(),
                amount);
      recipient()->updateLog("振替 入金",
             DateTime(), amount);
    }
    gui->displayScreen(SUCCESS_DEPOSIT_SCREEN);
    endTransaction();
 }


これはユースケースをほぼ逐語的に展開したものである。エンドユーザのメンタルモデルに見られる、ロジックの自然な構成に従ってロジックが多くのクラス境界に分散しているのと比べると、この方が理解しやすい。われわれはこれをメソッドフルなロールと呼ぶ。これについては次のセクションでより詳細に見ていく。

すべてのオブジェクトには2種類のノウハウがある

ロールの中核には、汎用的で抽象的なアルゴリズムが埋め込まれている。そのアルゴリズムには血肉がなく、実際に何かを実行することはできない。ある時点において、これがすべてオブジェクトまで降りてくる。このオブジェクトはドメインモデルが組み込まれたものと同じである


DCIによって解決される根本的な問題は、人々がオブジェクトと呼ばれる統合された単一のものについて、頭の中で2つの異なるモデルを持っているというものだ。1つはシステムが何かについてのデータモデルであり、それは銀行とその口座について考える際の助けとなる。もう1つがシステムが何をするかについてのアルゴリズムモデルであり、これは口座間での資金の移動に関するようなものだ。ユーザは個々のオブジェクトとそれがドメイン内に存在していることを認識しているが、各オブジェクトはユーザのインタラクションに関するモデルからくるふるまいを実装する必要もある。このインタラクションは与えられたユースケースにおいて演じるべきロールを通じ、あるオブジェクトを別のオブジェクトと結びつけるのである。エンドユーザはこれら2つの見方がどのように整合するのかということについて、優れた直観を持っている。例えば、預金口座が振替ユースケースの振り込み元口座というロールにおいてなんらかの責任を果たすということを、エンドユーザは理解している。このこと、つまりロールの見方とデータの見方の間にある紐づけも、ユーザが持つ認識モデルの一部なのだ。われわれはこれをユースケースシナリオを実行する上でのコンテキストと呼ぶ。


このモデルを図3に示す。右側にはエンドユーザのロールを抽象化したものをインタフェースとしておいている(JavaC#C++では、純粋な抽象基底クラスを用いることができる)。これらはアーキテクチャの基本的な形式をとらえたものであり、要件やドメインに対する理解が発展するのに合わせて満たされなければならない。上部には、右側の抽象化されたロールのクローンとして始まり、そのメソッドを実装しなければならないロールが置かれている。振替ユースケースにおける振り込み元アカウントという概念のために、ランタイムにロールを実行するオブジェクトの型とは独立してメソッドを定義することができるのだ。これらのロールは総称型("generic type")であり、JavaやAdaのジェネリクスC++のテンプレートに似ている。これら2つの構成要素が一緒になって、ロールとアルゴリズムに関するエンドユーザのメンタルモデルをコードにおいてとらえるのだ。




図3. 構造とアルゴリズムをクラスにおいて一体化させる


左側にあるのはわれわれの古くからの友人であるクラスである。ロールクラスはどちらもエンドユーザの頭の中にあるものだ。両者は実行時に1つのオブジェクトへと融合する。多くのプログラミング言語においてオブジェクトはクラスに由来するが、われわれはこれをあたかもドメインのクラスがロール形式の別クラスに存在する業務関数をサポートしているかのように見せかけなければならない。コンパイル時には、プログラマユースケースシナリオとそこで操作されるエンティティの両方についてエンドユーザが持つモデルと向き合わなければならないのだ。プログラマが、エンドユーザの頭の中にある二分法に敬意を表しつつ、これらのモデルを異なるプログラミング構造において別々にとらえる際の役に立ちたいのだ。通常、クラスはこのようなふるまいないしアルゴリズムを一緒に集めておく上で自然な場所だと考えられている。しかし、これらのコンパイル時の概念がオブジェクトと呼ばれる単一のものの中で実行時に共存しているという、一見すると矛盾に見えるものと向き合わねばならない。


これは難しそうだが、エンドユーザですら頭の中でこれら2つの見方の一部を一体化させることができているのだ。だからエンドユーザは、預金口座が振替処理において、振り込み元口座というロールを演じられることを理解している。預金口座が本来、口座番号と名付けられたあるキーによって、現金を今いくら入手できるのかについて語る方法であるにもかかわらずである。したがって操作を振替ユースケースシナリオから切り取り、データ処理能力のない預金口座オブジェクトに追加することができなければならない。図3が示しているのは、このようなロールロジック(弧)とクラスロジック(角丸四角)が結びつけられる様子である。既に預金口座には報告書出力、残高の増加、減少といった細かい作業のための操作がある。これら後者の操作は(実行時)にドメインクラス(コンパイル時の生成物)によってサポートされる。ユースケースシナリオ紐づくよりダイナミックな操作は、オブジェクトが演じるロールに由来する。ユースケースシナリオから切り取られた一連の操作がロールと呼ばれるものだ。これらについて、コンパイル時には閉じた形(ソースコード)においてとらえたいと思うし、対応するユースケースが実行時にやってきた時にはオブジェクトがそれをサポートできるようにしたい。したがって、図4に示すように、あるクラスのオブジェクトはそのクラスのメンバ関数をサポートするだけでなく、どんな時でもそれが演じるロールメンバ関数を、あたかも自分のものであるかのように実行できなけれあならないのだ。つまり、ロールのロジックをオブジェクトに注入し、それによって、オブジェクトがインスタンスの構築時にクラスから受け取るメソッドと同じように、ロジックがオブジェクトの一部となるようにしたいのだ。




図4. 構造とアルゴリズムをオブジェクトにおいて一体化させる


ここでは、どのようなロールを演じるように依頼されたとしても、それをサポートするのに必要となり得るロジックがすべてコンパイル時に組み込まれるよう各オブジェクトを設定した。しかし、うまくやれば、実行時に必要なロジックだけを各オブジェクトに注入することもできるし、これは与えられたロールにおける外見を支えるためにも、必要なことでもある。

ロールは共に機能する:コンテキストとインタラクション

振替をするためにATMに行く場合、頭の中には2つのオブジェクトがある(これが言わば、「私の預金口座」と「私の投資口座」だ)。また同様に、処理についての構想あるいはアルゴリズムもある。これは私と銀行の両方に納得のいく形で振り込み元口座から資金を取り出し、振り込み先口座に加えるというものだ。(おそらく私の預金口座が実際の銀行においてオブジェクトではないということは本当だろう。しかし、ATMの世界ではオブジェクトであるに違いない。仮にそうではなかったとしても、それが問題にならないよう、DCIには優れた汎用化の考え方がある。)その他にもう1つ、これら2つがどのようにマップされるのかということについての考え方を持っている。ATMとインタラクトする時にこの紐づけ、つまりコンテキストを構築しているのである。


私が最初にすることは、おそらく振替をしたいと考えることだろう。これにより振替シナリオが頭の中の「キャッシュ」に蓄積され、それと同時にロールとアルゴリズムに関するある種の表象がコンピュータメモリにロードされる。前述した通り、これらのシナリオはロールという観点からとらえることができる。


次にすることは、おそらく振り込み元口座と振り込み先口座を選択することだろう。コンピュータの内部では、プログラムがこれら2つのオブジェクトをメモリにロードされる。これらは処理を持たないデータオブジェクトであり、このオブジェクトが知っているのは自身の残高と、その残高を増やしたり減らしたりするにはどうすればよいかといったいくつかのシンプルな事柄である。同じように口座オブジェクト単独では、データベーストランザクションのような複雑なことは何も理解していない。これはシステムが何をするかということに関連する高次の業務関数であり、そして個々のオブジェクトはシステムが何であるかに関するものなのだ。高次の知識が存在するのはオブジェクト自体ではなく、これらのオブジェクトがあるインタラクションにおいて果たすロールの中なのである。


ここで私は振替を実行したいと考える。この処理が行われるためには、私の預金口座が振り込み元口座のロールを演じ、私の投資口座が振り込み先口座のロールを演じる必要がある。ロールが持つメンバ関数を対応するオブジェクトに対して魔法のように貼りつけることができ、そのうえで単にインタラクションを実行すると想像してほしい。各ロールの「メソッド」は貼りつけ対象となるオブジェクトのコンテキストにおいて実行されるのである。そしてこれはまさにエンドユーザのとらえ方なのだ。次のセクションでは、オブジェクトに対して、ロールを演じるのに必要な知識をどのように与えるのかについて見ていく。差し当たりは、委譲かミックスインかアスペクトのような何かを使うと考えておいて欲しい。(実際には、これらの手法はどれも何かしらの問題を抱えているので、使うのは何か別のものになる。それでも、その解決方法はこれら既存の技術と結びつくものだ。)




図5. ロールをオブジェクトに紐づける


コントローラ及びモデルからコンテキストに伸びている矢印が示しているのは次のことだ。すなわち、コントローラが紐づけについてのヒントを与えるようなパラメタをいくつか渡して紐づけを初期化するということと、モデルオブジェクトがほとんどの紐づけ対象の元になっているということだ。メソッドレスなロールはアプリケーションコードのための識別子である。これによってコードが(コントローラとメソッドフルなロールの内部で)アクセスするオブジェクトは、その型の識別子を通じて得られるサービスを提供するのだ。これはコンパイル時にチェックを行う言語においては特に役に立つものとなる。与えられたオブジェクトが要求されたロールの機能をサポートできるということについて、ささやかな保証をコンパイル時に提供するからである。


この時には、振替に影響を与える必要があるオブジェクトはすべてメモリ内にある。前述した通り、エンドユーザも頭の中に、関連するロールの観点から振替をを行うための処理ないしアルゴリズムを持っているのだ。そのアルゴリズムを実行できるコードを取り出す必要はあるが、その後は適切なロールを持った適切なオブジェクトを並べ、コードを実行するだけだ。図5に示した通り、アルゴリズムとロールからオブジェクトへの紐づけを行うのはコンテキストオブジェクトである。特定のユースケースにおいて実際にアクタとなるオブジェクトをどのように探し出せばよいかということをコンテキストは「知っている」のであり、ユースケースシナリオにおいて適切なロールに「キャスト」するのだ(ここで「キャスト」という単語は演劇的な意味で使っている。ただ、もしかしたら、いくつかプログラム言語の型システムにおける意味もあるかもしれない)。典型的な実装においては、各ユースケースに対して1つコンテキストオブジェクトがあり、各コンテキストはユースケースに含まれる各ロールのための識別子を保持している。コンテキストがやらなければいけないのは、ロールの識別子を適切なオブジェクトに結びつけることだけだ。そうすれば、やらなけらばいけないことは、そのコンテキストにとって「エントリ」となるロール上にあるトリガメソッドをキックすることだけで、それによってコードが実行される。実行はナノ秒で終わるかもしれないし、数年かかるかもしれない。しかしこれは処理に関するエンドユーザのモデルを反映したものなのだ。


以上をもって、DCIアーキテクチャが完成した。

  • データ:これはドメインオブジェクトの中にあり、ドメインクラスに由来している。
  • コンテキスト:要求に応じて、アクティブなオブジェクトをシナリオにおけるポジションに位置づける。
  • インタラクション:ロールの観点からエンドユーザのアルゴリズムを記述するもの。ロールもアルゴリズムもエンドユーザの頭の中に存在する。


図5に示すように、コンテキストは紐づけテーブルと考えることができる。このテーブルは、ロールのメンバ関数(テーブルの行)をオブジェクトのメソッド(テーブルの列に対応するのはオブジェクトである)に紐づける。テーブルはプログラマによって提供されるコンテキストオブジェクト内の業務知識に基づいて埋められる。この業務知識は与えられたユースケースにおいてどのオブジェクトがどのロールを演じるべきかを知っている。あるロールの1メソッドは、ロールのインタフェースを通じて他のロールとインタラクトするのであり、また同時にコンテキストが提供するロールとオブジェクトの紐づけに支配される。コントローラ内のコードは、広くコンテキストの観点から業務ロジックを扱うことができる。オブジェクトが持つ知識の詳細は、すべてロールの観点から記述することができ、さらにコンテキストを通じてオブジェクトに変換される。


このようなスタイルのプログラミングについての1つの考え方として、これがポリモルフィズムの1形態、それもプログラミング言語によってサポートされている以上に高度なものだというものがある。実際に、すべてのポリモルフィズムプログラマ支配下に置くことができる。ロールは明確にオブジェクトに紐づけられるのであり、あらゆるロールメソッドの呼び出しは静的に結びつけられるのだ。これによってコードは簡単に分析できるようになり、静的に理解できるようになる。これをオブジェクト指向プログラミング言語における通常のメソッドディスパッチと比較してみよう。通常のメソッドディスパッチでは、コードを静的に分析しても、どこでメソッドの呼び出しが終了するのか判断できないのが一般的である。


いくつかの実装において、コンテキストも業務ロジックメソッドをドメインオブジェクトに注入する。これは特にPythonRubyのようなダイナミックランゲージに基づく実装ではそう言える。C++C#においては、クラスレベルで注入することにより、業務ロジックメソッドをすべて「プレロード」するのが普通である。これはコンパイル時に行うことができる。Scalaにおいては、ドメインクラスからのオブジェクト生成をハイブリッドに行うことができる。これはインスタンス生成の時にロールメソッドを注入するのだ。(Scalaは実はC++C#と同じことをやっているのだが、インスタンス生成時におけるミックスインを定義する上で優れたシンタックスを持っている。Scalaコンパイラは業務ロジックメソッドをすべてプレロードするような匿名クラスを生成するのであり、その瞬間においてのみクラスはインスタンス化されるのである。)オブジェクトは命を吹き込まれる時、基底となるドメインクラスのふるまいとユースケースに対応するロールのふるまいの両方を提供するハイブリッドな型を与えられるのだ。


ネストされたコンテキスト
リッチなコンテキストオブジェクトを作って、自己完結したロール間の関連についてのすべてのサブグラフを定義することを考えることもできる。この関連は静的なものなので、独自にある種のドメインを構築することができる。このようなコンテキストオブジェクトがいくつかパブリックメソッドを持っていれば、ドメインオブジェクトのようにふるまうこともできる。預金口座について考えてほしい。これはオブジェクト指向についての簡易な学習コースにおいて、クラスの例として誤って用いられることが多いものだ。預金口座は実はロールが持つふるまいを集めたものである。そのロールとはトランザクションであり、トランザクションログであり、監査証跡である。もし預金口座がコンテキストであれば、これらのロールを与えられたメソッドに応じて適切なオブジェクトに紐づけることができる。(例えば、口座の残高を計算したり、月次報告書を生成したりというように)その上で、適切なロールにおいて処理が開始される。預金口座コンテキストは「高次の」コンテキストオブジェクトによって、ドメインオブジェクトとして使用され、下位のコンテキストオブジェクトを呼び出すことができる。これは多層のドメインモデルをサポートする強力な概念だ。

トレイト:特徴と目的を結合する設計上のトリック

問題はこれをどうやって実現するかということだ。そしてその結論が、トレイトと呼ばれる概念である。ロールが(エンドユーザの頭の中から引き出すための)分析概念であるとすれば、トレイトはロールを表象する汎用的な設計概念である。そしてその実装はプログラミング言語によって異なる。例えば、C++においてトレイトは、メンバ関数コンパイル時に具象クラスとコンポーズされるテンプレートとして表象される。それによってオブジェクトは実行時にクラスのふるまいとテンプレートのふるまいの両方を実行できるのだ。

. . . .
template <class ConcreteAccountType> class TransferMoneySourceAccount {
public:
   void transferTo(Currency amount) {
        beginTransaction();
        if (self()->availableBalance() < amount) {
        . . . .
}

. . . .

class SavingsAccount:
    public Account,
    public TransferMoneySourceAccount<SavingsAccount> {
public:
    void decreaseBalance(Currency amount) {
        . . . .
    }
}

. . . .


Scalaにおいてトレイトは、なんとトレイトと呼ばれる言語の構成概念によって実装される。このメソッドはオブジェクトに対してインスタンス生成時に注入されるのだ。

. . . .

trait TransferMoneySourceAccount extends SourceAccount {
  this: Account =>

  // このコードはレビューとテストが可能である!
  def transferTo(amount: Currency) {
    beginTransaction()
    if (availableBalance < amount) {
        . . . .
    }
}

. . . .
val source = new SavingsAccount with TransferMoneySourceAccount
val destination = new CheckingAccount with TransferMoneyDestinationAccount
. . . .


Squeak Smalltalkにおいては、メソッドフルなロールをSqueak Traitによって実装する。このSqueak TraitはSchärli4によって開拓された規約に従って用いられるものだ。そして、このトレイトのメソッドは適切なデータクラスに注入される。これはコンパイル時に対象クラスのメソッドテーブルに追加することで実現される。

. . . . 

RoleTrait named: #TransferMoneySource 	
   uses: {}
   roleContextClassName: #MoneyTransferContext  
   category: 'BB5Bank-Traits' 
   . . . . 

TransferMoneySource>>transfer: amount
   self balance < amount 
      ifTrue: [self notify: 'Insufficient funds'. ^self].
   . . . .

Object subclass: #Account
   uses: TransferMoneySource
   instanceVariableNames: 'balance'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'BB5Bank-Data'

DCIの実装は、C#/.NET(Christian Horsdal Gammelgaard)、Ruby (Steen Lenmann)、Python (David Byers and Serge Beaumont)、Groovy (Lars Vonk)においても存在する。Qi4J環境(Richard Öberg and Steen Lehmann)はJava環境においてトレイトを表現する能力を示している。

DCIの特性

  • ユースケースが示す要件に現れるような、ユーザが持つ主要な概念をとらえるために、われわれはロールを用いる。ロールとは、エンドユーザの認識モデルにおけるファーストクラスのコンポーネント("first-class component")であるため、コードにも反映させるべきだと考えられる。意味的には、これらのロールはJavaや.Netにおけるインタフェースの概念に近い。しかしインタフェースが用いられるのは、ふるまいを設計する際の全体的な形態をとらえる時だけである。究極的には、われわれの目標はユースケースをコードにおいてとらえることであり、そのためには言語が持つ他の機能を使用する。この手法はプログラミング言語によって異なる。SqueakScalaにおいては、トレイトを直接用いることができる。C++においては、テンプレートを用いることでトレイトを模することができる。その他の言語ではクラスに対してなんらかのトリックを使い、あるクラスのメソッドを他のオブジェクトに関連づける。
  • 経験や暗黙の知識に由来する深いドメインの概念をスマートデータとして表現するためにオブジェクトを用いる。かつて、われわれはCRCカード(クラス、責務、コラボレーション)を用いて責務をクラスに割り当てた。しかし、責務を示すのはクラスではなく、ロールなのだ。このことは、ある活動についての記憶を引き出すよう誰かに頼むことで明らかになる。本の整列や口座間の資金の移動というタスクについて語る時には、このようなトランザクションのほとんどは、クラスではなくロールを含んでいるのである。
  • ソフトウェアはオープンクローズ原則を表現する。オープンクローズ原則が継承だけに基礎をおいていては、貧弱な情報隠蔽という結論に至る。DCI形式によって、ドメインクラスとロール両方の統合が保たれる。クラスは修正に対して閉じられ、ロールを注入することにより、拡張に対して開かれる。
  • DCIはアジャイルソフトウェア開発と自然に整合する。DCIによってプログラマは、エンドユーザのメンタルモデルと直接的につながることができるようになる(このことは、単に顧客がプロセスやツールの代わりに、エンドユーザとのインタラクションに従事できるようにするということを凌駕している)。したがって、顧客が持つ共通語彙を使用し、顧客と並んでコードを読むことができるようになる(契約交渉よりも顧客とのコラボレーション)。タスクの順序の形式について論理的に考えることができる(このことは動作するソフトウェアを提供するチャンスを飛躍的に向上させる。少なくともプログラマがこれを理解できるのであり、エンドユーザのメンタルモデルと変換する距離ははるかに短くなるからだ)。そして、大事なことだが、これによって頻繁に変化するユースケースの部分と、安定したドメインの部分が分離されるのであり、そのことで変化が受け入れられる。これらのメリットはアジャイルマニフェストhttp://www.agilemanifesto.org)の条項に直接結びつけられるものだ。
その他

ユーザの頭の中には他のモデルも確かにある。いくつかのソフトウェアエンジニアリング陣営における共通の寵児が、業務ルール("business rule")だ。DCIはルールをとらえるのに便利な場所を提供していない。これは原始的なオブジェクト指向の欠点がインタラクションををとらえるのに失敗していたことであったのと同じような欠点であろう。状態と状態遷移に代表されるその他多くの形式主義も、データとモデルの利用に由来する作られたモデルと見ることができる。例えば、車のアクセルを踏んで意味があるのは、ギアが入っている状態の時だけだということを私は理解している。このような集合による状態マシンの表象は、正当なアクセル「メッセージ」が、ギアの「状態」においてのみ起こり得るということを示しているかもしれない。しかし、このような遷移はロール(アクセル、ギア、エンジン)の観点から記述される一連の処理ステップとして見ることもできる。軽く直観に照らしてみれば、後者のモデルの方がわれわれの直観によく合っていることが分かる。マニア的な視点には状態マシンモデルの方がよく合うのかもしれないが。


しかし、このような結論を裏付ける確固とした研究結果があるわけではない。完全に明らかにするという意味では、この領域における更なる研究が実りをもたらすであろうと信じている。そうはいっても、全体像が欠けていてもより信頼できる絵に至ることはできるのであり、DCIはその方向へ向けた重要なステップであると考えられるのである。

歴史の中のDCI

DCIは多くの点において、過去のパラダイムを統一したものだ。これらのパラダイムは長い間オブジェクト指向プログラミングのサイドカーとして現れてきたものである。

  • アスペクト指向プログラミング(AOP)には他の使い方もあるが、DCIはAOPの多くの使い方に答えるものであり、アスペクトの目的の多くは関心の分離である。AOPの根底にある基本原則と並んで、DCIもリフレクションやメタプログラミングの深い形式に根差している。アスペクトと異なり、ロールは集約と構築がうまくできる。コンテキストが一連のロールの間にある関連についてスコープを閉じているのに対し、アスペクトは適用されるオブジェクトとペアになるだけである。
  • 多くの点において、DCIはミックスイン形式戦略を反映している。ただし、ミックスインそれ自体にはコンテキストのセマンティクスにあるようなダイナミクスは存在しない。
  • オブジェクトのロジックから手続き的なロジックを分離できるという意味において、DCIはマルチパラダイムデザインが持つシンプルな目標の多くを実装している。しかし、マルチパラダイムデザインが提示する強引なテクニックに比べて、DCIはより優れた結合と凝集を生み出すことができる。

脚注

1.A Personal Computer for Children of All Ages, Alan Kay, Xerox Palo Alto Research Center, 1972 http://www.mprove.de/diplom/gui/Kay72a.pdf
2.IFIP-ICC Vocabulary of Information Processing; North-Holland, Amsterdam, Holland. 1966; p. A1-A6.
3.Working with Objects: The Ooram Software Engineering Method., Reenskaug, Trygve, Wold, P., Lehne, O. A., Greenwich: Manning Publications, 1996.
4.Traits: The Formal Model, N. Schärli, Nierstrasz, O; Ducasse, S; Wuyts, R; Black, A; “Traits: The Formal Model,” Technical Report, no. IAM-02-006, Institut für Informatik, November 2002, Technical Report, Universität Bern, Switzerland, Also available as Technical Report CSE-02-013, OGI School of Science & Engineering, Beaverton, Oregon, USA

謝辞

多くのコメントとScalaのコードサンプルを提供してくれたBill Bennrs氏に感謝します。