2016-02-24 186 views
3

第二種方法

我有一個家庭的,提供一個可擴展的應用程序(即,不固定的)設置的,其可以通過各種插件中使用的變量。動態依賴注入

的例子是:

  1. 日誌事件源
  2. 計算的結果源
  3. 對系統資源的使用
  4. 源的服務表現指標源
  5. ...

插件可以使用任何組合的SE。

樣品插件可以是:

  • 的定製錯誤記錄器,使用1
  • 定製的統計模塊,使用2
  • 使用3和4
甲服務表現工具

我想達到的是

  • presen t給出一組可用的插件列表(當沒有日誌事件源時,您不應該能夠選擇自定義錯誤記錄器)。
  • 得到一個簡單而安全的方式來將變量傳遞給插件,以避免由於缺少變量而導致運行時錯誤。

獎勵將允許插件可選地要求變量,例如,一個插件,需要4.並且可選地使用3.如果可用(但也可用其他)。

第一種方法

我想實現某種形式的「動態依賴注入」的。 讓我用一個用例來解釋它。

我正在構建一組將用於一系列應用程序的庫。 每個應用程序都可以提供一組不同的變量,這些變量可用於某些需要這些變量的「處理程序」。 根據具體的可用變量,必須確定可用處理程序的數量,因爲只有在處理程序可以訪問所有必需的變量時才能使用處理程序。 此外我正在尋找一種方法來儘可能安全地進行調用。編譯時間可能不可能,但「檢查一次,之後再也不會失敗」就可以。

下面是第一個草圖。在這個階段一切仍然可以改變。

class DynamicDependencyInjectionTest 
{ 
    private ISomeAlwaysPresentClass a; 
    private ISomeOptionalClass optionA; 
    private ISomeOtherOptionalClass optionB; 
    private ISomeMultipleOption[] multi; 

    private IDependentFunction dependentFunction; 

    void InvokeDependency() 
    { 
     // the number of available dependencies varies. 
     // some could be guaranteed, others are optional, some maybe have several instances 
     var availableDependencies = new IDependencyBase[] {a, optionA, optionB}.Concat(multi).ToArray(); 
     //var availableDependencies = new IDependencyBase[] { a }; 
     //var availableDependencies = new IDependencyBase[] { a, optionA }.ToArray(); 
     //var availableDependencies = new IDependencyBase[] { a, optionB }.ToArray(); 
     //var availableDependencies = new IDependencyBase[] { a , multi.First() }; 

     //ToDo 
     // this is what I want to do 
     // since we checked it before, this must always succeed 
     somehowInvoke(dependentFunction, availableDependencies); 

    } 

    void SetDependentFunction(IDependentFunction dependentFunction) 
    { 
     if (! WeCanUseThisDependentFunction(dependentFunction)) 
      throw new ArgumentException(); 

     this.dependentFunction = dependentFunction; 
    } 

    private bool WeCanUseThisDependentFunction(IDependentFunction dependentFunction) 
    { 
     //ToDo 
     //check if we can fulfill the requested dependencies 
     return true; 
    } 


    /// <summary> 
    /// Provide a list which can be used by the user (e.g. selected from a combobox) 
    /// </summary> 
    IDependentFunction[] AllDependentFunctionsAvailableForThisApplication() 
    { 
     IDependentFunction[] allDependentFunctions = GetAllDependentFunctionsViaReflection(); 
     return allDependentFunctions.Where(WeCanUseThisDependentFunction).ToArray(); 
    } 

    /// <summary> 
    /// Returns all possible candidates 
    /// </summary> 
    private IDependentFunction[] GetAllDependentFunctionsViaReflection() 
    { 
     var types = Assembly.GetEntryAssembly() 
      .GetTypes() 
      .Where(t => t.IsClass && typeof (IDependentFunction).IsAssignableFrom(t)) 
      .ToArray(); 

     var instances = types.Select(t => Activator.CreateInstance(t) as IDependentFunction).ToArray(); 
     return instances; 
    } 


    private void somehowInvoke(IDependentFunction dependentFunction, IDependencyBase[] availableDependencies) 
    { 
     //ToDo 
    } 
} 

// the interfaces may of course by changed! 

/// <summary> 
/// Requires a default constructor 
/// </summary> 
interface IDependentFunction 
{ 
    void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies); 
    Type[] RequiredDependencies { get; } 
} 

interface IDependencyBase { } 
interface ISomeAlwaysPresentClass : IDependencyBase { } 
interface ISomeOptionalClass : IDependencyBase { } 
interface ISomeOtherOptionalClass : IDependencyBase { } 
interface ISomeMultipleOption : IDependencyBase { } 


class BasicDependentFunction : IDependentFunction 
{ 
    public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies) 
    { 
     ; 
    } 

    public Type[] RequiredDependencies 
    { 
     get { return new[] {typeof(ISomeAlwaysPresentClass)}; } 
    } 
} 

class AdvancedDependentFunction : IDependentFunction 
{ 
    public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies) 
    { 
     ; 
    } 

    public Type[] RequiredDependencies 
    { 
     get { return new[] { typeof(ISomeAlwaysPresentClass), typeof(ISomeOptionalClass) }; } 
    } 
} 

class MaximalDependentFunction : IDependentFunction 
{ 
    public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies) 
    { 
     ; 
    } 

    public Type[] RequiredDependencies 
    { 
     // note the array in the type of ISomeMultipleOption[] 
     get { return new[] { typeof(ISomeAlwaysPresentClass), typeof(ISomeOptionalClass), typeof(ISomeOtherOptionalClass), typeof(ISomeMultipleOption[]) }; } 
    } 
} 
+0

正如馬克的答案所暗示的,您應該尊重KISS原則,並且不要試圖用一些模糊的DI重新發明輪子。想想那些會讀你的代碼的人。即使你在幾個月內也會頭疼,以便首先了解你的意圖...... – Fab

回答

2

保持簡單。讓插件依賴構造函數注入,它的優點是構造函數靜態地聲明每個類的依賴關係。然後使用反射來確定你可以創建什麼。

假設,例如,您有三個服務:

public interface IFoo { } 

public interface IBar { } 

public interface IBaz { } 

假設,而且,這三個插件存在:

public class Plugin1 
{ 
    public readonly IFoo Foo; 

    public Plugin1(IFoo foo) 
    { 
     this.Foo = foo; 
    } 
} 

public class Plugin2 
{ 
    public readonly IBar Bar; 
    public readonly IBaz Baz; 

    public Plugin2(IBar bar, IBaz baz) 
    { 
     this.Bar = bar; 
     this.Baz = baz; 
    } 
} 

public class Plugin3 
{ 
    public readonly IBar Bar; 
    public readonly IBaz Baz; 

    public Plugin3(IBar bar) 
    { 
     this.Bar = bar; 
    } 

    public Plugin3(IBar bar, IBaz baz) 
    { 
     this.Bar = bar; ; 
     this.Baz = baz; 
    } 
} 

很明顯,Plugin1需要IFooPlugin2需要IBarIBaz。第三類Plugin3有點特別,因爲它有一個可選的依賴關係。雖然它要求IBar,但如果可用,它也可以使用IBaz

您可以定義使用一些基本的反射來檢查它是否會是可能創造各種插件的情況下,根據現有的服務作曲:

public class Composer 
{ 
    public readonly ISet<Type> services; 

    public Composer(ISet<Type> services) 
    { 
     this.services = services; 
    } 

    public Composer(params Type[] services) : 
     this(new HashSet<Type>(services)) 
    { 
    } 

    public IEnumerable<Type> GetAvailableClients(params Type[] candidates) 
    { 
     return candidates.Where(CanCreate); 
    } 

    private bool CanCreate(Type t) 
    { 
     return t.GetConstructors().Any(CanCreate); 
    } 

    private bool CanCreate(ConstructorInfo ctor) 
    { 
     return ctor.GetParameters().All(p => 
      this.services.Contains(p.ParameterType)); 
    } 
} 

正如你所看到的,你配置一個帶有一組可用服務的Composer實例,然後可以使用候選列表調用GetAvailableClients方法以獲得一系列可用插件。

您可以輕鬆地將Composer類擴展爲還可以創建所需插件的實例,而不是僅告訴您哪些插件可用。

您可能能夠在某些DI容器中找到此功能。 IIRC,Castle Windsor公開了一個測試器/ Doer API,如果MEF支持這樣的功能,我也不會感到驚訝。

以下xUnit.net參數化測試演示了上述Composer的工作原理。

public class Tests 
{ 
    [Theory, ClassData(typeof(TestCases))] 
    public void AllServicesAreAvailable(
     Type[] availableServices, 
     Type[] expected) 
    { 
     var composer = new Composer(availableServices); 
     var actual = composer.GetAvailableClients(
      typeof(Plugin1), typeof(Plugin2), typeof(Plugin3)); 
     Assert.True(new HashSet<Type>(expected).SetEquals(actual)); 
    } 
} 

internal class TestCases : IEnumerable<Object[]> 
{ 
    public IEnumerator<object[]> GetEnumerator() 
    { 
     yield return new object[] { 
      new[] { typeof(IFoo), typeof(IBar), typeof(IBaz) }, 
      new[] { typeof(Plugin1), typeof(Plugin2), typeof(Plugin3) } 
     }; 
     yield return new object[] { 
      new[] { typeof(IBar), typeof(IBaz) }, 
      new[] { typeof(Plugin2), typeof(Plugin3) } 
     }; 
     yield return new object[] { 
      new[] { typeof(IFoo), typeof(IBaz) }, 
      new[] { typeof(Plugin1) } 
     }; 
     yield return new object[] { 
      new[] { typeof(IFoo), typeof(IBar) }, 
      new[] { typeof(Plugin1), typeof(Plugin3) } 
     }; 
     yield return new object[] { 
      new[] { typeof(IFoo) }, 
      new[] { typeof(Plugin1) } 
     }; 
     yield return new object[] { 
      new[] { typeof(IBar) }, 
      new[] { typeof(Plugin3) } 
     }; 
     yield return new object[] { 
      new[] { typeof(IBaz) }, 
      new Type[0] 
     }; 
     yield return new object[] { 
      new Type[0], 
      new Type[0] 
     }; 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 
} 
+0

感謝您的詳細解答和您的優秀書籍。在此期間,我發現了一種解決方案,它依賴於某種屬性注入,但在這種情況下,構造函數注入是優越的。 'Composer'幫助我找出哪個插件可用,但我仍然需要做實際的實例化。我可以通過使用標準DI容器來實現這一點,並使用 - 留在溫莎城堡 - 通過註冊'IFoo'與'Component.For ().Instance(myClass.instanceOfIFoo)'?如果我這樣做,我還可以用Castle Windsor的「測試」功能替換作曲家嗎? – Onur

+0

@Onur利用Castle Windsor這樣的現有庫來實現'CanCreate'和'Create'功能是有意義的;爲了證明這個原則,我主要在我的答案中加入了Composer類。只要像Castle Windsor這樣的DI容器可以回答問題*,您是否可以創建此類型的對象?*您應該能夠實現所需的功能。自從我認真使用任何DI容器以來,我已經有一段時間了,所以我不記得API的詳細信息,但是IIRC幾家DI容器可以回答這樣的問題。 –

+0

我設法根據你的想法得到一個運行Castle.Windsor的原型(參見我的答案)。 – Onur

0

教訓:

  1. 原本我以爲我需要每個呼叫方法注入這是複雜的,因爲:

    • 這是很難分辨的依賴性會之前達成實例化類
    • 方法簽名不是完全類型安全的,或者不是非通用接口的一部分。
    • 儘管可以是非標準的,因此不容易通過現有系統
  2. 然後,我通過加入所需的依賴關係作爲屬性切換到一種屬性注射,每個綁定到接口所支持,從而使所需的依賴關係很容易被發現。調用本身是無參數的。

    • 這使得更容易找出哪個插件可以使用。
    • 「如果這個插件實現了設置這個依賴項的屬性的接口,設置它」的鏈可以在調用無參數方法之前填充依賴項。
    • 如果省略了一個調用,則依賴性不會被設置。
  3. 可能正確的做法是使用構造函數注入,因此可以使用標準工具。調用本身也是無參數的,所以它很適合在界面中使用。

以下是Mark解決方案的更完整版本,包括解析組件。它使用Castle.Windsor,xUnit,Shouldly和Resharper的NotNull, CanBeNull屬性。

進一步的工作是通過注入解析工具工廠(因爲它必須接受來自主機的實例,我們不能直接傳入解析器)來消除對Castle.Windsor的直接依賴。

public interface IFoo { } 

public interface IBar { } 

public interface IBaz { } 

/// <summary> 
/// Needed to invoke the plugin 
/// </summary> 
public interface IPlugin 
{ 
    void Invoke(); 
} 

public class Plugin1 : IPlugin 
{ 
    public readonly IFoo Foo; 

    public Plugin1([NotNull] IFoo foo) 
    { 
     if (foo == null) throw new ArgumentNullException("foo"); 
     this.Foo = foo; 
    } 

    public void Invoke() 
    { 
     ; 
    } 
} 

public class Plugin2 : IPlugin 
{ 
    public readonly IBar Bar; 
    public readonly IBaz Baz; 

    public Plugin2([NotNull] IBar bar, [NotNull] IBaz baz) 
    { 
     if (bar == null) throw new ArgumentNullException("bar"); 
     if (baz == null) throw new ArgumentNullException("baz"); 
     this.Bar = bar; 
     this.Baz = baz; 
    } 
    public void Invoke() 
    { 
     ; 
    } 
} 

public class Plugin3 : IPlugin 
{ 
    public readonly IBar Bar; 
    public readonly IBaz Baz; 

    public Plugin3([NotNull] IBar bar, [CanBeNull] IBaz baz = null) 
    { 
     if (bar == null) throw new ArgumentNullException("bar"); 
     this.Bar = bar; ; 
     this.Baz = baz; 
    } 
    public void Invoke() 
    { 
     ; 
    } 
} 

public class Bar : IBar 
{ 
} 

public class SampleHostTest 
{ 


    [Fact] 
    void SampleHostCanResolvePlugin3ButNot1And2() 
    { 

     var bar = new Bar(); 
     var plugins = Assembly.GetAssembly(typeof(SampleHost)) 
       .GetTypes() 
       .Where(t => t.IsClass && typeof(IPlugin).IsAssignableFrom(t)) 
       .ToArray(); 

     var sut = new SampleHost(bar, plugins); 

     sut.IsPluginSupported(typeof(Plugin1)).ShouldBeFalse(); 
     sut.IsPluginSupported(typeof(Plugin2)).ShouldBeFalse(); 
     sut.IsPluginSupported(typeof(Plugin3)).ShouldBeTrue(); 
    } 

    [Fact] 
    void ResolvePlugin3() 
    { 

     var bar = new Bar(); 
     var plugins = Assembly.GetAssembly(typeof(SampleHost)) 
       .GetTypes() 
       .Where(t => t.IsClass && typeof(IPlugin).IsAssignableFrom(t)) 
       .ToArray(); 

     var sut = new SampleHost(bar, plugins); 

     sut.IsPluginSupported(typeof(Plugin3)).ShouldBeTrue(); 

     sut.CreateAndInvokePlugin(typeof(Plugin3)); 
     // no exception => succeeded 
    } 



} 

public class SampleHost 
{ 
    private readonly IBar bar; 
    private readonly IWindsorContainer container; 
    private Type[] plugins; 

    public SampleHost(IBar bar, IEnumerable<Type> plugins) 
    { 
     this.bar = bar; 
     this.plugins = plugins.ToArray(); 
     this.container = new WindsorContainer(); 
     container.Register(Component.For<IBar>().Instance(this.bar)); 

     foreach (var plugin in this.plugins) 
     { 
      container.Register(Component.For(plugin).ImplementedBy(plugin).LifestyleTransient()); 
     } 
    } 

    public bool IsPluginSupported(Type type) 
    { 
     var result = container.Kernel.HasComponent(type) && 
        container.Kernel.GetHandler(type).CurrentState == HandlerState.Valid; 
     return result; 
    } 

    public void CreateAndInvokePlugin(Type type) 
    { 
     Assert.True(IsPluginSupported(type)); 

     var plugin = container.Resolve(type)as IPlugin; 

     Debug.Assert(plugin != null, "plugin != null"); 
     plugin.Invoke(); 
    } 


}