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