次の方法で共有


patterns & practices

パターンとプラクティスに頼る

Alex Homer

目次

パターンとプラクティス
ガイド
抽象化に対処する
マイクロソフトにおける依存関係の簡単な歴史
依存関係の逆転を適用する
拡張
まとめ

かつては、コンピュータ コードの記述は非常に困難な作業でした。私がホーム コンピュータのプログラミングを始めたのは思い出したくもないほど昔のことですが、そのころプログラムをある程度規則的かつスムーズに動作させようと思えば、マシン コードを使用するしかありませんでした。単純なワード プロセッシング ユーティリティでさえ、変数に対するメモリ割り当てを念入りに計画し、単純なタスク (画面上での文字の描画、キーボード入力のデコードなど) を実行するルーチンを記述し、各プロセッサ命令をアセンブラに入力する作業が必要で、完成までに数週間を要することもありました。

それに比べると、現在は強力なコードを実に容易に作成できます。私たちは、アプリケーションを短期間で効率的に構築するのに役立つ豊富なツール、高級言語、ランタイム フレームワーク、コード ライブラリ、およびオペレーティング システム サービスを当たり前のものとして利用しています。コードの作成は以前に比べてはるかに容易になりましたが、アプリケーションの作成作業はそうはなっていません。私たちが最近のアプリケーションに求めるものは、これまでよりもずっと多くなっています。これらのアプリケーションには、ネットワークを介した通信、他のアプリケーションやサービスとの対話、対話性と応答性に優れたユーザー インターフェイスの公開、リッチなメディアとグラフィックのサポートなどの機能が不可欠です。そしてもちろん、堅牢性、安全性、信頼性、管理性を兼ね備えていなければなりません。

実際に、プログラミング言語、オペレーティング システム、プラットフォーム サービス、およびフレームワークの継続的な成長と進化なくして、最近のアプリケーションに対する要件を満たすことはほぼ不可能です。このようなアプリケーションの大幅な複雑化に伴い、私たちはコードを簡素化および編成する手段を探さざるを得なくなりました。ここ数年、さまざまなプログラミング手法とデザイン手法のテストが繰り返され、進化を遂げています。コンポーネント化やオブジェクト指向のほか、新しいところではサービス指向などがこれに当たります。

パターンとプラクティス

ツールとフレームワークの進化に伴い、コードを迅速かつ簡単に記述できるようになりました。また、新しい言語とプログラミング スタイルのおかげで、高品質のコードを簡単に記述できるようになりました。その一方で、ここ 10 ~ 15 年の間にアプリケーションの作成能力に最も大きな影響を与えたのは、ソフトウェア デザイン パターンが進化し、普及してきたことです。

デザイン パターンは、アプリケーションのデザイン作業や開発作業で繰り返し生じる一般的な問題を示し、それらに対処するためのテクニックを提供するものです。デザイン パターンには、アーキテクチャ上の問題を解決し、アプリケーションに求められるデザインの複雑性を解消するための最新の業界プラクティスも示されています。コードとソリューション要素の構成は今日のソフトウェア デザインの核です。パターンはこれらの要素を簡素化および編成するための手段であり、アプリケーションのパフォーマンス、柔軟性、および保守性を最大化する絶好の機会を提供してくれます。

今日私たちが当たり前のように利用している基本的なデザイン パターンの多くをまとめた、Erich Gamma、Richard Helm、Ralph Johnson、John M. Vlissides (通称 "ギャング オブ フォー") による革新的な書籍『オブジェクト指向における再利用のためのデザインパターン』(ソフトバンク クリエイティブ、1999 年) が出版されて以降、開発者とデザイナが利用できるデザイン パターンの数は驚異的な割合で増え続けています。ソフトウェアの作成作業のほぼすべての側面がデザイン パターンと実装パターンにかかわるようになり、それらすべてを単純に文書化することは不可能なタスクと化しました。そのため、デザイナと開発者は、専門分野に特化し、各自の専門領域で活用できるパターンを学ぶ傾向にあります。

ガイド

2002 年、マイクロソフト社の patterns & practices グループは『.NET のアプリケーション アーキテクチャ: アプリケーションとサービスの設計』ガイドを発行し、アーキテクトが Microsoft .NET Framework を基にアプリケーションを構築する際に役立つ基本的なデザイン ガイダンスと専門家による助言を提供しました。しかし、テクノロジは変化し続けています。このガイドで説明されているデザインと開発の基本は今も変わらず有効ですが、元のガイドでは .NET Framework の一部の新機能や、新しいタイプのアプリケーションをデザインする際に生じる問題が扱われていません。

ここ数か月の間、業界のさまざまな専門家 (マイクロソフト外部の人物も含む) から構成されるチームが一丸となって、Microsoft .NET Framework に基づくソリューションの設計とデザインについて幅広く解説する改訂版のガイドの作成に取り組んできました。『Microsoft Application Architecture Guide 2.0 (マイクロソフト アプリケーション アーキテクチャ ガイド 2.0)』には、アーキテクチャの原則とパターンの概要、それにデザインと開発のプラクティスに関する最新の考え方が示されています。このガイドには、これらの要素を特定の種類のアプリケーションに適用するためのガイダンスも含まれているほか、実装を容易にする数多くのプラットフォーム サービスとコード ライブラリの概要が記されています。

もちろん、よく使われる言葉を借りれば、ソフトウェアとシステムのアーキテクチャは、人生、世界、そしてあらゆる物事に関する疑問を内包するものです。1 冊のガイドですべての疑問に答えることはできません。また、あらゆるシナリオを余すところなくカバーすることもできません。とはいえ、.NET Framework ベースのアプリケーションのデザインに携わって間もない開発者、別のプラットフォームから .NET Framework に移行した熟練のデザイナ、特定の情報やアドバイスを求めている経験豊富なアーキテクチャ、.NET Framework が提供する幅広いシナリオと機会に関心のある方は、この新しいガイドから必要な情報を得ることができます。

抽象化に対処する

業界のプラクティスを示すために、このガイドには、ほとんどすべての種類のアプリケーションをデザインする際に適用すべき一般的な原則が列挙されています。それには、関心の分離の維持、抽象化による層およびコンポーネント間の疎結合の実装、サービス ロケーション機能の実装、ログ記録やセキュリティをはじめとする横断的な懸念事項の管理などが含まれます。これらの原則は、望ましいものとはいえ目的に関連性がないように思えるかもしれません。しかし、ある手法を利用すれば、複数のデザインの原則を簡単に適用できます。依存関係の逆転の原則は、具象的な実装ではなく抽象化を通じて関心を分離することを表します。デザイン パターンの観点からこれを実現するには、制御の反転 (IoC) パターンと関連の依存関係の注入 (DI) パターンを適用します。

理論は実に単純です。各クラスまたはコンポーネントが何らかの操作やプロセスを実行するために使用する実際の具象型をデザイン時に指定する代わりに、型マップと登録済みの型で構成したコンテナから、これらのクラスやコンポーネントが適切なオブジェクトを取得できるようにします。たとえば、図 1 のシンプルなアプリケーションで示すように、データ アクセス コンポーネントにログ コンポーネントのサービスが必要な場合があります。タスク固有のコードとアプリケーション固有のコードから横断的なコードを適切に抽出しているため、選択できるログ コンポーネントは複数あるはずです。それはおそらく、アプリケーションのデバッグの際に使用するために設計されたログ コンポーネント、アプリケーションを社内の独自のネットワーク上で実行する際に使用するログ コンポーネント、そして Microsoft System Center などの監視環境を使用するエンタープライズ レベル システムでの実行に最適なログ コンポーネントの 3 つだと思われます。

fig01.gif

図 1 階層化と個別のログ コンポーネントを使用するシンプルなアプリケーション

それぞれ、特定の環境や各種データ ストアで使用するために設計されたデータ アクセス コンポーネントも複数あるはずです。そこで、ビジネス層では、現在の展開またはランタイム環境に応じて、適切なデータ層コンポーネントを選択する必要があります。同じように、アプリケーションで繰り返し使用するサービス (電子メール メッセージ送信サービス、データ変換サービスなど) もあるでしょう。依存関係の注入は、実行時にアプリケーションでサービスのインスタンス (新しいインスタンスと既存のインスタンスのどちらか) を取得するのに役立つサービス ロケーション機能の役割を果たします。

これらの各例は、アプリケーションのある部分が別の部分に依存するようすをうまく表しています。オブジェクトを密に結合することなくこれらの依存関係を解消することこそ、依存関係の逆転の目的です。

マイクロソフトにおける依存関係の簡単な歴史

依存関係の逆転の原則は随分以前から利用されていますが、それを開発者がマイクロソフト プラットフォーム上で動作するアプリケーションで実装するのを支援する機能が登場したのは、比較的最近です。事実、Java の世界で著名な開発者がマイクロソフトのキャンパスを訪れたときに、「マイクロソフトの人間はだれ 1 人として "依存関係の注入 (Dependency Injection)" という綴りを書くことができないと思われている」と語ったという話があります。これは明らかに都市伝説ですが、開発者が一般的なパターンの多くを実装する際に役立つツールは、社内のほとんどの部署で優先事項として扱われてきませんでした。

ただし、Microsoft patterns & practices グループは、社内組織でありながら主要な製品開発チームや部署に属していないという特殊な位置にあります。p&p の目標は、"予測可能な結果をもたらすための実証済みプラクティス" というスローガンに示されているとおり、マイクロソフト プラットフォームを基に優れたアプリケーションをデザインおよび構築するのに役立つ、ガイダンス、ツール、ライブラリ、フレームワーク、その他多数の機能を開発者に提供することです。MSDN の patterns & practices のホーム ページをざっと確認してみてください。patterns & practice が幅広い資産を提供していることがわかります。

これらの資産のうちの一部の製品 (Enterprise Library、複合アプリケーション フレームワーク、ソフトウェア ファクトリなど) には、依存関係の注入パターンが活用されています。これらの資産 (特に、元の複合アプリケーション ブロック (CAB)) の開発中に、再利用と高度な構成が可能な依存関係の注入メカニズムが必須だということが明らかになりました。そのため、チームは Object Builder の元のバージョンを構築したのです。

Object Builder はほぼ完全に構成可能であり、p&p をはじめとする、マイクロソフトのあらゆる部署が提供する幅広い製品に使用されています。ただし、Object Builder の使用は一筋縄ではいきません。複雑なオブジェクトを受け取る膨大な数のパラメータが必要であるうえ、必要な構成を適用するために処理する必要のあるさまざまなイベントを公開するためです。Object Builder を CAB プロジェクトの一部として文書化する初期の取り組みで、たいへんな作業になることがすぐに判明しました。また、Object Builder は単なる依存関係注入コンテナではなく、DI パターンと IoC パターンを実装するための一般的な要件という観点からすると、機能が過剰なようにも思えました。

Object Builder は、Enterprise Library 4.0 の開発中に、簡素化ではなく速度と効率性の向上を目的に更新されました。また、マイクロソフトが初めて提供する主要な依存関係注入メカニズム (直接の対象は DI パターンと IoC パターンの実装を試みる開発者) で使用できるように、微調整も行われました。Object Builder は、Unity (コンストラクタの挿入、プロパティの挿入、およびメソッド呼び出しの挿入をサポートする、軽量で拡張可能な依存関係注入コンテナ) の基盤です。

Unity は、オブジェクトの作成を簡素化する機能 (特に、作成するオブジェクトの構造と依存関係が階層的である場合)、実行時か、構成を通じて要件を抽象化する機能、横断的な懸念事項の管理を簡素化する機能、コンポーネントの構成をコンテナまで遅延させることで柔軟性を高める機能を提供します。サービス ロケーション機能を備えているため、ASP.NET Web アプリケーションにおいても、クライアントはコンテナを格納またはキャッシュできます。

2008 年初頭に最初のバージョンがリリースされて以降、Unity は、依存関係の逆転を実装するための既定のメカニズムとして、p&p のさまざまな資産で利用されてきました。Unity は、下位互換性を維持したまま進化も続けています。Enterprise Library 内の機能を有効化するために使用することも、スタンドアロンの DI コンテナとして使用することもできます。最新のリリースでは、インスタンスと型のインターセプトを (プラグイン拡張機能を通じて) 実装する機能が追加されました。これにより、ポリシーの挿入をはじめとするアスペクト指向プログラミング手法の実装が可能になりました。

Unity からは、モバイル デバイスやスマート フォン用に設計されたきわめて軽量な実装など、特定のタスクや要件向けの DI コンテナの実装も生み出されました。同時に、Unity と Enterprise Library の領域では、Enterprise Library をサードパーティのコンテナ メカニズムで使用できるようにする機能の開発が計画されているほか、Unity 向けの新機能を有効にする追加拡張機能も提供されています。

依存関係の逆転を適用する

このような歴史に関する小話は終わりにして、例に挙げたアプリケーションに戻りましょう。前に説明した、関心の分離、抽象化、疎結合などの目的を達成するためには、依存関係の逆転の原則をどのように適用すればよいのでしょうか。その答えは、適切な型と型マッピングを含む Unity などの依存関係注入コンテナを構成し、アプリケーションで適切なオブジェクトのインスタンスを実行時に取得および挿入できるようにすることです。図 2 に、Unity アプリケーション ブロックを使用してこのコンテナを実装する方法を示します。このケースでは、データ コンポーネントとログ コンポーネントのインターフェイス定義間の型マッピングと、アプリケーションで使用するこれらのインターフェイスの特定の具象実装をコンテナに設定しています。

fig02.gif

図 2 依存関係の注入により、コンテナの構成に基づいて実行時に適切なコンポーネントを選択できる

実行時にビジネス層はコンテナにクエリを行い、現在のマッピングに応じて、適切なデータ層コンポーネントのインスタンスを取得します。次にデータ層がコンテナにクエリを行い、格納されているインターフェイス型のマッピングに応じて、適切なログ コンポーネントのインスタンスを取得します。別の方法として、データ コンポーネントとログ コンポーネントをそれぞれの基本クラスから継承し、コンテナへの登録時にこれらの基本型と継承する具象型間のマッピングを行うこともできます。

このような、型とインスタンスを解決するためのコンテナ主導型のアプローチを採用すると、開発者はデータ コンポーネントとログ コンポーネントの実装を自由に変更できるようになります (ただし、マップされたインターフェイスを実装するか、マップされた基本クラスから継承するなどして、これらの実装が必要な機能を提供し、適切なインターフェイスを公開している場合)。コンテナの構成は、型、型マッピング、またはオブジェクトの既存のインスタンスを登録するコンテナのメソッドを使用して、実行時にコードで設定できます。また、構成のソースやファイル (web.config ファイル、app.config ファイルなど) から登録内容を読み込んでコンテナに設定することもできます。

型のインスタンスを複数登録するには、名前でそれぞれを定義し、名前を指定して各型を解決します。登録時にオブジェクトの有効期限を指定することもできます。サービス オブジェクトをシングルトンとして登録するか、特定の有効期限 (スレッドごとの有効期限など) を使用することで、サービス ロケーション スタイルの機能を容易に実現できます。次のコード例に、コンテナへの型マッピングの登録の例を示します。

C#
// Register a mapping for the CustomerService class to the IMyService interface. 
myContainer.RegisterType<IMyService, CustomerService>();

// Register the same mapping using a mapping name. 
myContainer.RegisterType<IMyService, CustomerService>("Data");

// Register the first mapping, but as a singleton. 
myContainer.RegisterType<IMyService, CustomerService>(
                         new ContainerControlledLifetimeManager());

注: このコード例では、クラス名だけを使用してクラスと型を参照しています。構成ファイル内で型の別名定義を使用して、完全修飾されたクラスの型名に別名を付けることができます。これにより、構成ファイルを使用する場合にコンテナへの登録を簡素化できます。

オブジェクトのインスタンスを取得するには、次の例に示すように、型、インターフェイスの型、または基本クラスの型を指定して (名前を使用して型を登録した場合は名前を指定)、単純にコンテナにクエリを行います。すると、コンテナによって型が解決されます。型が登録されている場合は、適切なオブジェクトのインスタンスが作成されるか、返されます。型が登録されていない場合は、コンテナによって単純にその型の新しいインスタンスが作成され、返されます。アイテムの型が登録されていないのに、なぜコンテナを通じてそのアイテムを解決するのでしょうか。それは、Unity をはじめとする多くの DI コンテナ メカニズムが提供する非常に便利な追加機能 (コンストラクタ、プロパティ セッター、およびメソッド呼び出しの挿入を使用してオブジェクトを挿入する機能) を活用できるからです。

C#
// Retrieve an instance of the mapped IMyService concrete class. 
IMyService result = myContainer.Resolve<IMyService>();
// Retrieve an instance by specifying the mapping name. 
IMyService result = myContainer.Resolve<IMyService>("Data");

たとえば、コンテナを通じてオブジェクトのインスタンスを作成する場合、Unity はコンストラクタを調べ、適切な型のインスタンスをコンストラクタのパラメータに自動的に挿入します。前のシンプルなアプリケーションの例に戻って考えてみると、データ アクセス コンポーネントには、ログ コンポーネントへの参照をパラメータとして受け取るコンストラクタがある可能性があります。このパラメータの型がコンテナに登録されているログ コンポーネントのインターフェイスまたは基本クラスであれば、Unity はマップされている型を解決し、インスタンスを作成してデータ コンポーネントのコンストラクタに渡します (図 3 を参照)。マッピングの登録以外の操作は必要ありません。

図 3 オブジェクトをコンストラクタのパラメータに挿入する

C#
// In main or startup code:
// Register a mapping for a logging component to the ILogger interface.
// Alternatively, you can specify this mapping in a configuration file. 
myContainer.RegisterType<ILogger, MyLogger>();

...

// In data access component:
// Variable to hold reference to logger.
private ILogger _logger = null;

// Class constructor. Unity will populate the ILogger type parameter.
public DataAccessRoutines(ILogger myLogger)
{
  // store reference to logger
  _logger = myLogger;
  _logger.WriteToLog("Instantiated DataAccessRoutines component");
}

つまり、コンテナの構成を変更するだけで、アプリケーションで使用する実際の具象型を変更できるのです。この構成の変更は、デザイン時または実行時 (この場合は構成を編集) に行うか、コードで環境から収集し、コンテナ内のマッピングを作成または更新するために使用する何らかの値に基づいて動的に行います。デバッグ ログ コンポーネントを必要に応じて接続することも、古いログ コンポーネントのスピードが遅すぎると判断したときに "超高速の" 新しいログ コンポーネントを接続することもできます。一方、システム管理者は、変化し続ける環境と運用上の問題に合わせて、実行時にアプリケーションの動作を監視、管理、および調整できるように、必要に応じて構成を更新できます。

同様に、2 つのオブジェクト間に依存関係がある場合 (Model-View-Presenter (MVP) パターンを実装したときにビューがプレゼンタに依存している場合など) は、依存関係の注入を利用して、これらのクラス間の結合を緩くすることができます。次の例に示すように、単純にビュー クラスのプロパティをプレゼンタの型または基本クラスの型として定義し、プロパティに Dependency 属性を付けてください。

C#
// Variable to hold reference to controller.
private IPresenter _presenter;

// Property that exposes the presenter in the view class. Unity will inject 
// this automatically because it carries the Dependency attribute.
 [Dependency]
public IPresenter MyViewPresenter
{
  get { return _presenter; }
  set { _presenter = value; }
} 

注: 属性を使用すると、挿入するプロパティを最も簡単に指定できます。(クラスがコンテナに結合されるのを避けて) 属性を使用しない場合は、構成ファイルか Unity API を代わりに使用して、挿入するプロパティを指定できます。

コンテナで解決することでビューを作成する場合、Unity は Dependency 属性を検出し、適切な具象プレゼンタ クラスのインスタンスを自動的に解決して、それをビュー クラスのプロパティの値として設定します。Unity に付属するクイック スタートの例では、このアプローチが Windows フォーム アプリケーションを通じて示されています。そこでは、実際にアプリケーションのメインの Form がコンテナによって解決されています。これにより、Unity はコンストラクタとプロパティ セッターの挿入を使用して、アプリケーション全体で複数の依存関係を作成、設定しています。

拡張

Unity には DI 関連の豊富な機能が用意されていますが、開発者は常に何らかの付加価値を提供したいと考えるものです。Unity に関する課題は、できるだけ多くの要件を満たせるように汎用性を維持しながらも、開発者独自の要件に合わせて調整できるように、拡張性も備えていなければならないという点でした。これはコンテナ拡張を使用することで解決できます。コンテナ拡張により、オブジェクトの作成と取得の管理に関するほとんどすべての処理を行うことができます。

例として、Unity に付属するクイック スタートでは、単純な属性主導のパブリッシュ/サブスクライブ メカニズムを実装するコンテナ拡張が紹介されています。Unity はオブジェクトのインスタンスを作成するときに、そのイベント ハンドラをクラス ファイル内の属性に基づいて関連付けます。また他の例では、コンテナを通じて各型を作成または取得する際に、詳細なログ情報を生成しています。この情報は、複雑なアプリケーションのデバッグを行う際に役立ちます。

Unity を利用すると、コンテナ拡張を通じて基になる Object Builder メカニズムを操作できるため、このような大きな柔軟性を得ることができます。「でも Object Builder を扱うのは非常に難しく、十分に文書化もされていない」とご不満の方もおられるかもしれません。実際には、Unity のドキュメントでは、コンテナ拡張から Object Builder を操作する方法という観点から Object Builder に関する説明がなされています。また、クイック スタートの例には、自由に使用、変更できる多数のサンプル コードが示されています。

まとめ

アプリケーションのアーキテクチャとデザインに関しては、さまざまな見方があります。ISO/IEC 標準 42010:2007/IEEE 1471「Recommended Practice for Architecture Description of Software-Intensive Systems (ソフトウェア集約型システムのアーキテクチャ記述のための指針)」では、ソフトウェアのアーキテクチャは「システムのコンポーネント、コンポーネントどうしと環境との間の関係、およびその設計と進化を支配する原理に体現された、システムの基本的な構造」と定義されています。一方、Martin Fowler の著書『エンタープライズ アプリケーション アーキテクチャ パターン』(翔泳社、2005 年) には、「つまりアーキテクチャとは、何であれ重要な要素なのである」とあります。この見方の方が、ソフトウェア アーキテクチャの本質をずっと簡単にとらえることができます。

『Microsoft Application Architecture Guide 2.0 (マイクロソフト アプリケーション アーキテクチャ ガイド 2.0)』を読むと、この "重要な要素" とは何かを理解し、使いやすく高品質なアプリケーションをより迅速に効率よく構築できるようになります。この記事で説明したように、ある特定の領域 (依存関係の注入パターンと制御の反転パターンの活用) は、このガイドで推奨されている数々のデザイン目標を達成するうえで役立ちます。これには、関心の分離、抽象化による層間の疎結合の実装、サービス ロケーション、横断的な懸念事項の管理を容易にするための機能の実装などが含まれます。

Alex Homer は、Microsoft patterns & practices チームで働くドキュメント エンジニアです。生活、テクノロジ、および世界全般に関する彼の気ままなつぶやきは、https://blogs.msdn.com/alexhomer/ で読むことができます。