动态依赖注入

Onu*_*nur 4 c# dependency-injection dynamic

第二种方法

我有一系列应用程序,它们提供了一组可扩展(即非固定)变量,可供各种插件使用。

例子是:

  1. 日志事件的来源
  2. 计算结果的来源
  3. 系统资源使用的来源
  4. 绩效指标的来源
  5. ...

插件可以使用这些的任意组合。

示例插件可以是:

  • 自定义错误记录器,使用 1.
  • 自定义统计模块,使用 2.
  • 使用 3. 和 4. 的性能工具。

我想要实现的是

  • 给出可以使用的插件列表,给定此应用程序中存在的一组变量(当没有日志事件源时,您应该无法选择自定义错误记录器)。
  • 获得一种简单且安全的使用方式将变量传递给插件,这样就不会因为缺少变量而出现运行时错误。

一个好处是允许插件可选地需要一个变量,例如一个插件需要 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[]) }; }
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ann 5

把事情简单化。让插件依赖于Constructor Injection,它的优点是构造器静态地宣布每个类的依赖关系。然后使用反射来找出您可以创建的内容。

例如,假设您有三个服务:

public interface IFoo { }

public interface IBar { }

public interface IBaz { }
Run Code Online (Sandbox Code Playgroud)

此外,假设存在三个插件:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

很明显,Plugin1需要IFoo,并且Plugin2需要IBar和IBaz。第三个类,Plugin3,有点特殊,因为它有一个可选的依赖项。虽然它需要 IBar,但IBaz如果可用,它也可以使用。

您可以定义一个 Composer,它使用一些基本反射来检查是否可以根据可用服务创建各种插件的实例:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,您Composer使用一组可用服务配置一个实例,然后您可以GetAvailableClients使用候选列表调用该方法以获取一系列可用插件。

您可以轻松地扩展Composer该类,以便还能够创建所需插件的实例,而不仅仅是告诉您哪些可用。

您可能会在某些 DI 容器中找到此功能。IIRC,Castle Windsor 公开了一个Tester/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();
    }
}
Run Code Online (Sandbox Code Playgroud)