特集
Enterprise Library 2.0新機能

Enterprise Library 2.0を特徴づけるDI機能とは

アバナード株式会社 市川 龍太(patterns & practices Champion
2006/04/01
2006/04/03 更新
2007/01/31 更新
Page1 Page2 Page3

属性ベースのDI機能を提供するObjectBuilder

 ObjectBuilderはComposite UI Application Block(スマートクライアント・アプリケーションにModel-View-Presenterパターン*1を適用するためのApplication Block。以下CompUIAB)のために開発されたApplication Blockであり、それがCoreを構成するサブシステムとしてEntLib 2.0に採用されたという経緯がある。

*1 Model-View-Presenter(以下MVP)パターンは、Model-View-Controler(以下MVC)パターンのようにGUIアプリケーションを3つのコンポーネントに分離する点では同じであるが、MVCがModelからの通知によってViewを更新するのに対して、MVPはModelが直接Viewに通知することはせずに、Presenterを介してViewへ通知することで更新を行うところが異なっている。これによってViewの更新がPresenterに依存することになるが、MVCと比べると比較的実装が容易であるという特長がある。

 ObjectBuilderの機能をひと言で説明するなら「オブジェクトの生成/破棄という依存性(Dependency)を、アプリケーションの実行時に属性を使って注入する(Inject)」ということになるだろう。

 依存性注入はDependency Injection(以下DI)とも呼ばれ、「オブジェクト間の依存関係を実行時に動的に注入する」というパターンのことを指す。本稿ではDIについてこれ以上の詳細な解説は行わないが、DIの命名者でもあるマーチン・ファウラー氏のブログの記事「Inversion of Control Containers and the Dependency Injection pattern」(邦訳サイト)で詳細な解説がなされているため興味を持たれた方は参考にしてみるとよいだろう。

●ObjectBuilderが注入する依存性

 そもそもObjectBuilderが動的に注入する依存性とは何だろうか? それは文字どおり「オブジェクトの生成」という依存性である。例えば、オブジェクトAの中でオブジェクトBを利用する場合は、必ずオブジェクトBを生成しなければならず、それには事前にオブジェクトAがオブジェクトBを知っている必要があるため、ここにオブジェクト間の依存性が生まれることになる。

 そこでObjectBuilderは、このオブジェクトの生成という依存性を取り除き、実行時に動的に注入することで、オブジェクト生成に柔軟性を持たせることができるのである。

ObjectBuilderがオブジェクトBの生成という依存性を注入する

 それではこれからObjectBuilderの内部構造を見ていくとしよう。

●ObjectBuilderのアーキテクチャ

 ObjectBuilderは、オブジェクトの生成と破棄の実行を管理する「ストラテジ」、複数のストラテジを保持する「ステージ」、それからオブジェクトの生成に関する情報を保持する「ポリシー」という3つの要素によって構成されている。

 そしてPreCreation、Creation、Initialization、PostInitializationという4種類のステージでパイプライン*2を形成し、各ステージに登録されているストラテジが順番に実行されていくことでオブジェクトの生成を行い、ポリシーは各ストラテジへの入出力情報としての役目を担うのである。

*2 パイプラインとは、ある処理をシーケンシャルな複数の処理ステップに分割し、前ステップの出力データを入力値として次のステップを順に処理していくフローのことを意味している。
 
ObjectBuilderが実装しているデフォルトのパイプライン・モデル
ステージはオブジェクトの生成段階ごとに4種類存在する。
 ・PreCreation:オブジェクトを生成する前段階。
 ・Creation:オブジェクトの生成段階。
 ・Initialization:生成されたオブジェクトの初期化段階。
 ・PostInitialization:生成されたオブジェクトを返す段階。

 これはObjectBuilderがデフォルトで実装しているパイプライン・モデルであり、PreCreationステージのTypeMappingStrategyから最後のPostInitializationステージのBuilderAwareStrategyまでが順番に実行されることで最終的にオブジェクトが生成されていくことになる。

 次の表はデフォルトで用意されているストラテジの一覧である。

クラス名 動作 入力 出力
PreCreation TypeMapping Strategy 任意のインターフェイスや抽象クラスから、それらを継承する任意の型へ変換する IType Mapping Policy
Singleton Strategy 指定された型のオブジェクトがすでに存在する場合はそれを返し、存在しない場合は新しく生成する Locator
Constructor Reflection Strategy InjectionConstructorAttributeの付いたコンストラクタを探し、ConstructorPolicy(ICreationPolicyを継承)を作成する リフレクション ICreation Policy
Property Reflection Strategy ParameterAttributeの付いたプロパティを探し、PropertySetterPolicyを作成する リフレクション IProperty Setter Policy
Method Reflection Strategy InjectionMethodAttributeの付いたメソッドを探し、MethodPolicyを作成する リフレクション IMethod Policy
Creation Creation Strategy コンストラクタまたはActivatorクラスを使ってオブジェクトを生成する。ISingletonPolicyがあるならLocatorに登録する ICreation Policy ISingleton Policy Locator
Initialization Property Setter Strategy IPropertySetterPolicyを基にプロパティに値を設定する IProperty Setter Policy
Method Execution Strategy IMethodPolicyを基にメソッドを実行する IMethod Policy
Post Initialization Builder Aware Strategy オブジェクト生成の完了を通知する
デフォルトで用意されているストラテジ一覧

 いままで解説してきたオブジェクト生成パイプラインを、図で表すと以下のようになる。

オブジェクト生成パイプラインの概念図

 これはあくまでObjectBuilderがデフォルトで用意しているパイプラインにすぎず、実際にObjectBuilderを利用してフレームワークやクラス・ライブラリを開発する場合は、用途に合ったストラテジを独自に用意し、ObjectBuilderが提供するいずれかのステージに追加して最適なパイプラインを構築する必要があるだろう。

 事実、EntLib 2.0では各Application Blockの用途に応じて独自のストラテジを定義しており、例えばCompUIABでもデフォルトのパイプラインを変更し、かつEventBrokerStrategyやControlSmartPartStrategyなどの独自のストラテジを追加することでCompUIABに最適化されたパイプラインを構築している。具体的には以下のようなパイプラインだ。

CompUIABが独自に定義しているパイプライン・モデル例
CompUIABは独自に定義したストラテジ(ピンク色の四角)をInitializationステージに登録している。

●ObjectBuilderの利用

 それでは実際に簡単なサンプル・プログラムを使ってObjectBuilderのDI機能を使ってみる。まずクラス・ライブラリ・プロジェクトを新規作成し、以下のクラスとインターフェイスを追加する。

public interface ITargetClass
{
  string GetValue();
  void SetValue(string val);
}

public class ConcreateTargetClass : ITargetClass
{
  private string _value;
  public string GetValue() { return this._value; }
  public void SetValue(string val) { this._value = val; }
}

public class SampleClass
{
    private ITargetClass _targetObject;

    [InjectionConstructor]
    public SampleClass(){ }

    [Dependency(CreateType = typeof(ConcreateTargetClass))]
    public ITargetClass TargetObject
    {
        get { return this._targetObject; }
        set { this._targetObject = value; }
    }

    [InjectionMethod()]
    public void SetTargetClass()
    {
      this._targetObject.SetValue("ConcreateTargetClassを生成");
    }
}
クラス・ライブラリ側のサンプル・プログラム
このサンプル・プログラムを実行するには、以下のアセンブリを参照設定に追加する必要がある。
 ・Microsoft.Practices.ObjectBuilder.dll

 Dependency属性はParameter属性の派生クラスであり、パラメータとして指定した「CreateType = typeof(ConcreateTargetClass)」によって、そのプロパティ(この例ではTargetObjectプロパティ)はConcreateTargetClass型のオブジェクトを返すことになる。

 では次に、同じソリューションにコンソール・アプリケーションを追加し、以下のコードを記述する。

class Program
{
  static void Main(string[] args)
  {
    PolicyList[] noPolicies = { };
    Locator locator = new Locator();
    locator.Add(
      typeof(ILifetimeContainer), new LifetimeContainer());
    Builder builder = new Builder();

    SampleClass sample =
      builder.BuildUp<SampleClass>(
        locator, "Sample", null, noPolicies);
    ITargetClass target = sample.TargetObject;

    Console.WriteLine(target.GetValue());
    Console.ReadLine();
  }
}
コンソール・アプリケーション側のサンプル・プログラム
このサンプル・プログラムを実行するには、以下のアセンブリを参照設定に追加する必要がある。
 ・Microsoft.Practices.ObjectBuilder.dll

 このコンソール・アプリケーションを実行するとコンソール・ウィンドウに「ConcreateTargetClassを生成」と表示される。

 このサンプル・プログラムで何が起こったのかお分かりいただけただろうか? BuilderクラスのBuildUpメソッドが呼ばれると、デフォルトのオブジェクト生成パイプラインに従ってオブジェクト(ここではSampleClassオブジェクト)の生成が開始される。この際、各ステージでInjectionConstructor属性が付加されているコンストラクタ、Dependency属性が付加されているプロパティ(ここではTargetObjectプロパティ)、InjectionMethod属性が付加されているメソッド(ここではSetTargetClassメソッド)がリフレクションによって検査、実行されることで、最終的に目的のオブジェクト(ConcreateTargetClassオブジェクト)が生成されるのである。

 このように生成される側のクラス内で任意の属性を付加することで、オブジェクト生成パイプラインに属性や振る舞いを注入し、生成する側は何も意識することなく柔軟にオブジェクトを生成できる。これこそがObjectBuilderが提供する属性ベースのDI機能のメカニズムなのである。

 これによってオブジェクト間の結合度を低くすることが可能になり、結果として機能変更への対応が容易になったり、呼び出し先のクラスをモック・クラスに変更することでテストが容易になったりするなどのメリットがもたらされる。事実、CompUIABはObjectBuilderを利用することで、スマート・クライアント・アプリケーションを役割ごとに、SmallParts、Service、UIElement、WorkItem、Commandなどに分割し、それらを疎に結合することで徹底したモジュール化を実現している。

 なお、CompUIABは以下のMSDNサイトからダウンロードが可能である。

●ObjectBuilderがCoreに搭載された意味

 ここまでEntLib 2.0について解説を行ってきたのだが、それではEntLib 2.0がObjectBuilderをCoreに据えたことの本当の意義とは何だろうか?

 この命題について筆者なりの意見をいわせてもらうなら、それはObjectBuilderがEntLibを「ありのまま使う(AS IS)」段階から「自由に拡張していく(TO BE)」段階へと押し上げたことにあるのではないかと考えている。もちろん従来のEntLibでも十分に拡張性を意識した設計になっていたのだが、それはEntLib自体を拡張するための拡張性にすぎなかった。

 しかしEntLib 2.0は従来のEntLibが備えていた拡張性だけでなく、Patterns & Practicesが掲げるベスト・プラクティスを踏襲しながら、EntLib 2.0の上にさらなる枠組みを構築していくことで自由に拡張していける下地を築いたことこそがObjectBuilderを取り入れたことの本当の意義ではないだろうか。

 EntLibはバージョンを重ねるごとに確実に進化しており、恐らく今後も進化し続けていくことだろう。.NETが存在し続ける限りEntLibも存在し続け、.NETが進化し続ける限りEntLibも進化の歩みを止めることは決してない。そしてこれこそがEntLibがピュア.NETたるゆえんなのである。本稿がさらなる高みへのぼるための手助けになったなら幸いである。End of Article

 

 INDEX
  特集:Enterprise Library 2.0新機能
  Enterprise Library 2.0を特徴づけるDI機能とは
    1.EntLib 2.0の構成
    2.従来のEntLib 1.xとEntLib 2.0の相違点
  3.属性ベースのDI機能を提供するObjectBuilder
 
インデックス・ページヘ  「Enterprise Library 2.0新機能」

更新履歴

【2006/04/03】以下のような誤りがありました。お詫びして訂正させていただきます。

クラス・ライブラリ側のサンプル・プログラム

public class ConcreateTargetClass : ITargetClass
{
  public string GetValue() { return "ConcreateTargetClassを生成"; }
  public void SetValue(string val) { this._value = val; }
}
public class ConcreateTargetClass : ITargetClass
{
  private string _value;
  public string GetValue() { return this._value; }
  public void SetValue(string val) { this._value = val; }
}

【2007/01/31】以下のような誤りがありました。お詫びして訂正させていただきます。

クラス・ライブラリ側のサンプル・プログラム

[InjectionMethod()]
    public void SetTargetClass()
    {
      this._targetClass.SetValue("ConcreateTargetClassを生成");
    }
[InjectionMethod()]
    public void SetTargetClass()
    {
      this._targetObject.SetValue("ConcreateTargetClassを生成");
    }


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間