【ハウツー】

Beta版で試す! VS 2010と.NET 4.0の新機能(3) - MEFの機能と使い方

1 Managed Extensibility Frameworkの役割

    漆尾貴義  [2009/10/02]

    前回は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の説明である。

    関連したタグ

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

        マイナビニュースマガジン