2016-02-24 158 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(); 
    } 


}