简单注入器:如何跳过对容器中对象的验证

本文关键字:对象 验证 注入器 何跳过 简单 | 更新日期: 2023-09-27 18:05:26

我正在使用简单注入器通过构造函数注入将依赖项注入到我的对象中。

对于一组特定的对象(全部派生自一个公共抽象基类(,我注入一个工厂而不是具体对象,以便我可以在运行时确定应该注入哪个派生实例。工厂作为单一实例向容器注册,派生实例也是如此。

在向 DI 容器注册我的所有对象后,我调用 Container.Verify(( 方法来验证我的配置。问题是,此方法的实现会创建向容器注册的每个类型的实例。这些派生实例的创建成本很高,并且它们的创建具有副作用(它们来自正在更新以使用 DI 的旧代码(。长期我会摆脱副作用,但短期内这是不可行的。

如何告诉容器不验证这些派生实例?

我想保留 Verify(( 调用(至少对于调试版本(,但不能接受这些实例由 Verify(( 调用实例化。此外,它们需要在单例生活方式中注册,所以我不能只是不将它们注册到容器中。

我想出的一个解决方案是不向容器注册派生对象(从而使它们不可验证(,然后让(Singleton Lifestyle(工厂实现缓存它创建的第一个实例。它可以工作,但它很脏,如果有人在其他地方显式请求派生类型的实例(不太可能(,则会创建一个新实例(因为默认的生活方式是暂时的(。

这是我拥有的类结构的示例:

public class AbstractBase { }
public class Derived1 : AbstractBase { }
public class Derived2 : AbstractBase { }
public class Derived3 : AbstractBase { }
public class MyFactory
{
    private readonly Func<Derived1> _factory1;
    private readonly Func<Derived2> _factory2;
    private readonly Func<Derived3> _factory3;
    public MyFactory(Func<Derived1> factory1, Func<Derived2> factory2, Func<Derived3> factory3)
    {
        _factory1 = factory1;
        _factory2 = factory2;
        _factory3 = factory3;
    }
    public AbstractBase Create()
    {
        if (AppSettings.ProductType == ProductType.Type1)
            return _factory1();
        if (AppSettings.ProductType == ProductType.Type2)
            return _factory2();
        if (AppSettings.ProductType == ProductType.Type3)
            return _factory3();
        throw new NotSupportedException();
    }
}

以及 DI 容器的注册:

Container container = new Container();
container.RegisterSingle<Derived1>();
container.RegisterSingle<Derived2>();
container.RegisterSingle<Derived3>();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
container.RegisterSingle<Func<Derived2>>(() => container.GetInstance<Derived2>());
container.RegisterSingle<Func<Derived3>>(() => container.GetInstance<Derived3>());
container.RegisterSingle<MyFactory>();
container.Verify(); // <-- will instantiate new instances of Derived1, Derived2, and Derived3. Not desired.

简单注入器:如何跳过对容器中对象的验证

从你的问题中,我知道你非常清楚你当前方法的缺点,你目前正在处理一些无法在一次迭代中更改的遗留代码。但是由于其他人也会阅读本文,因此我想像往常一样指出,注入构造函数应该简单,快速和可靠。

有了这个,回答问题:不,没有办法在简单注射器中标记要跳过注册的注册。

在调用Verify()之前为容器创建的所有InstanceProducer实例都将得到验证(除非它们之前被垃圾回收(。通常,当您调用Register重载时,会为您隐式创建InstanceProducer实例,但您也可以通过调用 Lifestyle.CreateProducer 来创建新的InstanceProducer实例。这些生产者不会成为任何对象图的一部分(这意味着Simple Injector不会使用它们来自动连接其他类型的类型(,并且它们充当根类型。但是,容器仍然会跟踪这些生产者,并且验证也将适用于它们。

因此,这里的诀窍是在验证过程之后触发创建新InstanceProducer实例。可以通过调用 Lifestyle.CreateProducer 来执行此操作,也可以通过解析未注册的具体类型来执行此操作,就像您在示例中所做的那样。当然,解析未注册类型的缺点是默认情况下它被解析为暂时性。有两种方法可以解决这个问题。您可以自己缓存实例,也可以指示容器将该特定类型创建为单一实例。

自己执行缓存可能如下所示:

var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);

但是,自己缓存它的缺点是,您会使诊断系统失明,并且无法为您发现任何生活方式不匹配。因此,更好的选择是覆盖生活方式选择行为,如下所示:

// Custom lifestyle selection behavior
public class AbstractBaseDerivativesAsSingleton : ILifestyleSelectionBehavior {
    public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) {
        typeof(AbstractBase).IsAssignableFrom(implementationType)
            ? Lifestyle.Singleton
            : Lifestyle.Transient;
    }
}
// Usage
var container = new Container();
container.Options.LifestyleSelectionBehavior =
    new AbstractBaseDerivativesAsSingleton();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());

解决此问题的另一种方法是使用 Lifestyle.CreateProducer 调用自己创建InstanceProducers。由于您想要单例,因此必须致电Lifestyle.Singleton.CreateProducer。这些生产者需要在调用验证后创建,所以你仍然需要使用 Lazy 作为延迟机制:

// This factory should be part of your composition root, 
// because it now depends on the container.
public class MyFactory : IMyFactory
{
    private readonly Container container;
    private readonly Dictionary<ProductType, Lazy<InstanceProducer>> producers;
    public MyFactory(Container container) {
        this.container = container;
        this.producers = new Dictionary<ProductType, Lazy<InstanceProducer>>
        {
            {ProductType.Type1, new Lazy<InstanceProducer>(this.CreateProducer<Derived1>)},
            {ProductType.Type2, new Lazy<InstanceProducer>(this.CreateProducer<Derived2>)},
            {ProductType.Type3, new Lazy<InstanceProducer>(this.CreateProducer<Derived3>)}
        };
    }
    public AbstractBase Create() {
        return (AbstractBase)this.producers[AppSettings.ProductType].GetInstance()
    }
    private InstanceProducer CreateProducer<T>() where T : AbstractBase {
        Lifestyle.Singleton.CreateProducer<AbstractBase, T>(this.container);
    }
}

还可以考虑将您的工厂更改为调解员或代理。工厂通常不是正确的抽象,因为它们通常只会增加复杂性。相反,您可以创建一个代理,该代理采用相同的接口并将调用委托给真实实例(您仍然在后台使用类似工厂的行为(:

public class AbstractBaseAppSettingsSwitchProxy : AbstractBase
{
    private readonly IMyFactory factory;
    public AbstractBaseAppSettingsSwitchProxy(IMyFactory factory) {
        this.factory = factory;
    }
    public override void SomeFunction() {
        this.factory.Create().SomeFunction();
    }
}

使用此代理,您可以向任何消费者隐藏存在多个可能的 AbstractBase 实现的事实。消费者可以简单地与AbstractBase进行通信,就好像总有一个一样。这可以使您的应用程序代码更干净,使用者代码更简单,并使使用者更易于测试。

如果代理不是一个选项,您仍然可以考虑调解器模式。调解器的工作方式与上面的代理大致相同,但不同之处在于它有自己的接口。因此,它很像工厂(具有自己的接口(,但不同之处在于中介器负责调用委托的对象。它不会将实例返回给使用者。

我知道这些解决方案可能不适用于您,因为AbstractBase的结构。如果你有一个胖基类,其中有些方法虚拟而另一些方法不是,那么可能很难做到这一点。这些解决方案通常只能在设计良好的 (SOLID( 系统中运行良好。但实际上这就是一切的运作方式。我的经验是,如果没有 SOLID 代码,一切都会变得繁琐。作为软件开发人员,我们的主要工作之一是降低软件的总拥有成本,而实现这一目标的最佳方法之一是将 SOLID 原则应用于我们的软件。

不过最后一点。在我看来,您正在读取工厂内部的一些配置值。如果在应用程序的配置文件中定义了此值,则只能通过重新启动应用程序来更改该值(这是 IIS 自动为您执行的操作(。如果这是这样的配置值,您实际上根本不需要所有这些废话。您只需进行以下注册:

Container container = new Container();
container.RegisterSingle(typeof(AbstractBase, GetConfiguredAbstractBaseType()));
private static Type GetConfiguredAbstractBaseType() {
    switch (AppSettings.ProductType) {
        case ProductType.Type1: return typeof(Derived1);
        case ProductType.Type2: return typeof(Derived2);
        case ProductType.Type3: return typeof(Derived3);
        default: throw new NotSupportedException();
    }
}

当然,这又让我们回到了无法验证的最初问题。但是我们再次可以使用代理解决此问题:

public class LazyAbstractBaseProxy : AbstractBase
{
    private readonly Lazy<AbstractBase> lazy;
    public LazyAbstractBaseProxy(Lazy<AbstractBase> lazy) {
        this.lazy = lazy;
    }
    public override void SomeFunction() {
        this.lazy.Value.SomeFunction();
    }
}
Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
    Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<AbstractBase>(new LazyAbstractBaseProxy(
    new Lazy<AbstractBase>(() => (AbstractBase)lazy.Value.GetInstance()));

如果这是不可能的,你甚至可以跳过工厂,直接向消费者注入一个 Func:

Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
    Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<Func<AbstractBase>>(() => (AbstractBase)lazy.Value.GetInstance());

回答主要问题:如何跳过对容器中对象的验证。

你可以这样做:

Container container = new Container();
Lifestyle singletonLifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => !container.IsVerifying,
    trueLifestyle: Lifestyle.Singleton,
    falseLifestyle: Lifestyle.Transient);
container.Register<TConcrete>(singletonLifestyle);
您可以使用一个

Lazy<T>注册您的Func<T>,该将延迟加载InstanceProducer,如下所示:

private static Lazy<T> Lazy<T>(Func<T> func) => new Lazy<T>(func);
public static void RegisterDelayedFunc<T>(this Container container, Lifestyle lifestyle)
    where T : class
{
    var lazy = Lazy(() => lifestyle.CreateProducer<T, T>(container));
    container.RegisterSingleton<Func<T>>(() => lazy.Value.GetInstance());
}
Container container = new Container();
container.RegisterDelayedFunc<Derived1>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived2>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived3>(Lifestyle.Singleton);
container.RegisterSingleton<MyFactory>();