前回はF#などのプログラミング言語について説明したが、今回はManaged Extensibility Framework(MEF)について見てみることにする。

MEFを一言で説明するのは難しいのだが、無理やり簡潔にまとめるとするなら「プログラム開発において、インタフェースと実装を分離することで、プログラムの拡張性を上げる仕組み」とでもいう説明になる。同じように、インタフェースと実装を分離する仕組みとして有名なのは、Dependency Injection(DI)コンテナである。その意味では、MEFは.NET Framework版DIコンテナである、ということもできる。

DI(Dependency Injection)とは

Javaの世界ではそれなりに有名なDIコンテナではあるが、.NET開発においてはあまり普及していないように思われるので、DIという考え方について簡単に説明しておこう(DIについてすでに知っているという方は、この項を読み飛ばしてもらって構わない)。

DIが目指しているのは「インタフェースと実装の分離」ということである。DI(Dependency Injection)を日本語に訳すと「依存性の注入」というような意味になる。では、この「依存性」とは何を指しているのだろうか。

たとえば、次のようなコードがあるとしよう。

public class XmlParser
{
    public string Parse()
    {
        return "解析結果";
    }
}

public class Printer{
    public void PrintData(XmlParser parser)
    {
        string data = parser.Parse();
        //以下、何らかの処理が続く
    }
}

public class Main
{
    void Process()
    {
        Printer printer = new Printer();
        XmlParser parser = new XmlParser();
        printer.PrintData(parser);
    }
}

この例では、Printerクラスのインスタンスが、XmlParserクラスのインスタンスが持つParseというメソッドを参照している。この場合、XmlParserクラスが存在しなければPrinterクラスのPrintDataメソッドは動作しないことになる。言い換えれば、PrinterクラスはXmlParserクラスに依存しているということになるわけである。

このような「依存性」がプログラムの中に増えてくると、ある修正が別の箇所に影響を及ぼすことが増えてきたり、クラス単体でのテストを行うことが難しくなってくる。そこでDIの考え方では、クラス同士が直接参照しあうのではなく、可能な限りインタフェースを通して参照するような設計にするのである。JavaやC#のようなオブジェクト指向言語において「(文法事項としての)interface」を使用する理由と、基本的には同じだ。さらに、特定のクラスのインスタンスを生成する役割を、すべて「DIコンテナ」に任せてしまうのだ。

たとえば先ほどのコードを、DIを使うように修正するならば、次のようなコードになるだろう(ここではイメージをつかんでもらうために、擬似コードで書いている。.NET FrameworkにはDIContainerというクラスは存在しない)。

class XmlParser : IParser
{
    public string Parse()
    {
        return "解析結果";
    }
}

class Printer : IPrinter
{
    //XmlParserクラスを直接参照するのではなく、
    //IParserインタフェースを参照する
    public void PrintData(IParser parser)
    {
        string data = parser.Parse();
        //以下、何らかの処理が続く
    }
}

interface IParser
{
    string Parse();
}

interface IPrinter
{
    void PrintData(IParser parser);
}

public class Main
{
    void Process()
    {
        //インスタンスの生成はDIコンテナに任せる
        //したがって、「どのクラスのインスタンスを生成するか」を意識する必要はない
        IPrinter printer = DIContainer.GetById("printer1");
        IParser parser = DIContainer.GetById("parser1");
        printer.PrintData(parser);
    }
}

さらに別の設定ファイルにおいて、IDとクラスの関連づけを行うわけだ。たとえば次のようなコードになる(こちらも擬似的な設定ファイルである。雰囲気だけをつかんでいただきたい)。

<settings>
   <setting id="printer1" class="Printer" />
   <setting id="parser1" class="XmlParser" />
</settings>

これらのクラスやインタフェースと、依存性を記述した設定ファイルを組み合わせて、プログラムを動作させる役割は「DIコンテナ」が担当する。今見てきた例で言えば、実際にPrinterクラスやXmlParserクラスのインスタンスを生成する、というような処理をDIコンテナが行うわけである。

このような仕組みを使うことによって、クラス同士がインタフェースを介して結びつくことになるので、クラス間の密結合を防ぐことができる。また、動作時に生成するインスタンスを切り替えるときにも、既存のコードを修正することなく、設定ファイルを書き換えるだけでよくなるため、実装の切り替えやクラスごとの単体テストが容易になるのである。非常に簡単ではあるが、以上がDIの説明である。