依存性をクラスごとに構成ファイルに定義。はい、DIです。
マイクロソフトから提供されてるEntLib*1に含まれている DIコンテナを弄ってみた。
思っていたよりもいい感じにDIできますね。うんこれは便利。
Microsoft Enterprise Library 4.1 – October 2008
構成ファイル(*.config)から手軽に依存性の注入をしよう
DIコンテナを利用するのであれば、なるべく手軽に依存性を注入をしたい。というわけで、お試し的に以下のようにDIできるようにしてみた。
ジェネリクスで型を指定をして、その型の依存性注入済みインスタンスを取得できるような感じです。
(もちろん、各DIコンテナの定義は構成ファイル(*.config)に定義してある前提で。)
いつものように例はC#で書きますが、VB.NETでも同様のことができます。
ちなみに、SampleInfomation()メソッドはDIとは直接関係ありませんので、無視してください。
using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Reflection; using DISample.Shinobi; using Microsoft.Practices.Unity; namespace DISample { public class Program { private static void Main() { // 依存性を注入済みのインスタンスを取得 var naruto = Utility.GetDependencyInjectedInstance<Naruto>(); var kakashi = Utility.GetDependencyInjectedInstance<Kakashi>(); var sasuke = Utility.GetDependencyInjectedInstance<Sasuke>(); //うずまきナルトの忍術(注入された) naruto.Execute(); //はたけカカシの忍術 kakashi.Execute(); //うちはサスケの忍術 sasuke.Execute(); Console.WriteLine(); //ちょっとしたサンプル情報をば SampleInfomation(); Console.ReadLine(); } /// <summary> /// ちょっとしたサンプル情報 /// </summary> private static void SampleInfomation() { var asm = Assembly.GetExecutingAssembly(); Console.WriteLine("アセンブリ名:{0}", asm.FullName); var types = from t in asm.GetTypes() where t.IsInterface select t; Console.WriteLine(); foreach (var item in from t in asm.GetTypes() from itf in types where !t.IsInterface where t.GetInterface(itf.FullName) != null orderby itf.Name, t.Name select new { itf, t }) { Console.WriteLine("\t{0}インターフェイスを実装している型:{1}", item.itf.Name,item.t.Name); } Console.WriteLine(); foreach (Type t in from t in asm.GetTypes() from itf in types from p in t.GetProperties() from atr in p.GetCustomAttributes(typeof(DependencyAttribute), false) where !t.IsInterface where atr != null where p.PropertyType == itf orderby t.Name select t) { Console.WriteLine("\tDI可能な型:{0}", t.FullName); } } } }
お手軽感が伝わったでしょうか。
Utility.GetDependencyInjectedInstance
上記を実現するには、まず依存注入可能であることを表すIDependencyInjectable
定義する必要があります。以下のような感じで作ってみました。
using System.Reflection; using System.Linq; using Microsoft.Practices.Unity; namespace DISample { /// <summary> /// 依存注入可能であることを表すインターフェイス /// </summary> public interface IDependencyInjectable<T> where T : new() { /// <summary> /// 構成ファイルのDirectoryを取得または設定します。 /// (アセンブリのパスに対するDirectory) /// </summary> string ConfigurationDirectory { get; } /// <summary> /// コンテナ名(構成ファイル名)を取得または設定します。 /// 通常はクラス名を指定します。 /// </summary> string ContainerName { get; } } /// <summary> /// IDependencyInjectableExtentions /// </summary> public static class IDependencyInjectableExtentions{ /// <summary> /// 依存注入可能なプロパティを保持しているか否かを取得します。 /// </summary> /// <typeparam name="T"> /// 対象の型 /// 制約:new() /// </typeparam> /// <param name="self">自身</param> /// <returns> /// true :保持している /// false:保持していない /// </returns> public static bool IsDependencyInjectable<T>(this IDependencyInjectable<T> self) where T: new() { //依存注入可能なPropertyInfoを取得 var pi = from p in self.GetType().GetProperties() from atr in p.GetCustomAttributes(typeof(DependencyAttribute), false) where atr != null where p.PropertyType.IsInterface select p; return (pi.Count() > 0); } } }
上記、拡張メソッドIsDependencyInjectable
DependencyAttribute属性のプロパティの存在チェックです。
では、続きましてUtility.GetDependencyInjectedInstance
using System; using System.Configuration; using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.Configuration; namespace DISample { public static class Utility { /// <summary> /// 依存性注入済みインスタンスを取得します。 /// </summary> /// <typeparam name="T">対象の型</typeparam> /// <returns>依存性注入済みのTのインスタンス</returns> public static T GetDependencyInjectedInstance<T>() where T : IDependencyInjectable<T>, new() { T instance = Activator.CreateInstance<T>(); // DI対象が依存注入可能なプロパティを持っていない場合 if (!instance.IsDependencyInjectable<T>()) throw new InvalidOperationException("コンテナ対象(" + instance.ContainerName + "型)は依存注入可能なプロパティを持っていません。"); // DIコンテナを生成 var uc = new UnityContainer(); // unityセクション取得 var section = GetUnityConfigurationSection<T>(instance); // 構成ファイルの内容を適用 section.Containers[instance.ContainerName].Configure(uc); // 依存性を注入 return (T)uc.BuildUp<T>(instance); } /// <summary> /// 設定ファイルから依存注入情報(UnityConfigurationSection)を取得します。 /// </summary> /// <typeparam name="T">対象の型</typeparam> /// <param name="idi"></param> /// <returns></returns> private static UnityConfigurationSection GetUnityConfigurationSection<T>(IDependencyInjectable<T> idi) where T : IDependencyInjectable<T>, new() { //DI対象専用の構成ファイルより、unityセクションを読込 var map = new ExeConfigurationFileMap(); map.ExeConfigFilename = idi.ConfigurationDirectory + @"\" + idi.ContainerName + ".config"; var config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None); return (UnityConfigurationSection)config.GetSection("unity"); } } }
対象の*.config ファイルに対して構成ファイルの対応付けを定義する
ExeConfigurationFileMapクラスの利用がみそでしょうか。
もちろん、ConfigurationDirectoryの階層構造はnamespaceの階層構造に沿う感じで。
IDependencyInjectableインターフェイスを実装
依存性が注入されるやつら。
using System; using Microsoft.Practices.Unity; namespace DISample.Shinobi { public class Naruto : IDependencyInjectable<Naruto> { [Dependency] public INinjyutsu Ninjyutsu { get; set; } public void Execute() { Console.WriteLine(this.Ninjyutsu.Excute()); } #region IDependencyInjectable<Naruto> メンバ string IDependencyInjectable<Naruto>.ConfigurationDirectory { get { return "DependencyInjection"; } } string IDependencyInjectable<Naruto>.ContainerName { get { return this.GetType().Name; } } #endregion } public class Kakashi : IDependencyInjectable<Kakashi> { [Dependency] public INinjyutsu Ninjyutsu { get; set; } public void Execute() { Console.WriteLine(this.Ninjyutsu.Excute()); } #region IDependencyInjectable<Kakashi> メンバ string IDependencyInjectable<Kakashi>.ConfigurationDirectory { get { return "DependencyInjection"; } } string IDependencyInjectable<Kakashi>.ContainerName { get { return this.GetType().Name; } } #endregion } public class Sasuke : IDependencyInjectable<Sasuke> { [Dependency] public INinjyutsu Ninjyutsu { get; set; } public void Execute() { Console.WriteLine(this.Ninjyutsu.Excute()); } #region IDependencyInjectable<Naruto> メンバ string IDependencyInjectable<Sasuke>.ConfigurationDirectory { get { return "DependencyInjection"; } } string IDependencyInjectable<Sasuke>.ContainerName { get { return this.GetType().Name; } } #endregion } }
DependencyAttribute属性が指定されているプロパティに依存性が注入されます。
注入される依存性
注入される依存性の定義です。namespace DISample { public interface INinjyutsu { string Excute(); } public class Kagebunshin : INinjyutsu { public string Excute() { return "影分身の術"; } } public class Rasengan : INinjyutsu { public string Excute() { return "螺旋丸"; } } public class Raikiri : INinjyutsu { public string Excute() { return "雷切"; } } public class Sharingan : INinjyutsu { public string Excute() { return "写輪眼"; } } public class KatonGokakyu : INinjyutsu { public string Excute() { return "火遁豪火球の術"; } } public class Chidori : INinjyutsu { public string Excute() { return "千鳥"; } } }
文字列を返しているだけです^^;
構成ファイルに依存性を定義
最後に依存性を定義してある構成ファイルの内容および、実行結果を示します。
うずまきナルトの定義
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </configSections> <unity> <!--型の別名を定義--> <typeAliases> <typeAlias alias="INinjyutsu" type="DISample.INinjyutsu, ConsoleApplication1"/> <typeAlias alias="kag" type="DISample.Kagebunshin, ConsoleApplication1"/> <typeAlias alias="rsn" type="DISample.Rasengan, ConsoleApplication1"/> </typeAliases> <containers> <container name="Naruto"> <types> <type type="INinjyutsu" mapTo="kag"/> </types> </container> </containers> </unity> </configuration>
はたけカカシの定義
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </configSections> <unity> <!--型の別名を定義--> <typeAliases> <typeAlias alias="INinjyutsu" type="DISample.INinjyutsu, ConsoleApplication1"/> <typeAlias alias="srg" type="DISample.Sharingan, ConsoleApplication1"/> <typeAlias alias="rkr" type="DISample.Raikiri, ConsoleApplication1"/> <typeAlias alias="ktn" type="DISample.KatonGokakyu, ConsoleApplication1"/> </typeAliases> <containers> <container name="Kakashi"> <types> <type type="INinjyutsu" mapTo="rkr"/> </types> </container> </containers> </unity> </configuration>
うちはサスケの定義
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </configSections> <unity> <!--型の別名を定義--> <typeAliases> <typeAlias alias="INinjyutsu" type="DISample.INinjyutsu, ConsoleApplication1"/> <typeAlias alias="tdr" type="DISample.Chidori, ConsoleApplication1"/> <typeAlias alias="ktn" type="DISample.KatonGokakyu, ConsoleApplication1"/> </typeAliases> <containers> <container name="Sasuke"> <types> <type type="INinjyutsu" mapTo="tdr"/> </types> </container> </containers> </unity> </configuration>
実行結果
影分身の術
雷切
千鳥アセンブリ名:ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
IDependencyInjectable`1インターフェイスを実装している型:Kakashi
IDependencyInjectable`1インターフェイスを実装している型:Naruto
IDependencyInjectable`1インターフェイスを実装している型:Sasuke
INinjyutsuインターフェイスを実装している型:Chidori
INinjyutsuインターフェイスを実装している型:Kagebunshin
INinjyutsuインターフェイスを実装している型:KatonGokakyu
INinjyutsuインターフェイスを実装している型:Raikiri
INinjyutsuインターフェイスを実装している型:Rasengan
INinjyutsuインターフェイスを実装している型:SharinganDI可能な型:DISample.Shinobi.Kakashi
DI可能な型:DISample.Shinobi.Naruto
DI可能な型:DISample.Shinobi.Sasuke
んー、結構いい感じです。
構成ファイルに定義されている依存性が、正しく注入されていますね(^ω^)
*1:Enterprise Libraryはマイクロソフトが推進するオープンソース・ライブラリ