简单注入器:如何跳过容器中对象的验证 [英] Simple Injector: How can I skip verification of an object in the container

查看:49
本文介绍了简单注入器:如何跳过容器中对象的验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using Simple Injector to inject dependencies into my objects via Constructor Injection.

对于一组特定的对象(所有对象都从一个通用的抽象基类派生),我注入了一个工厂而不是具体的对象,以便可以在运行时确定应注入哪个派生实例.工厂和该容器作为派生实例在容器中注册为单例.

For a particular set of objects (all derived from a common abstract base class) I inject a factory instead of the concrete object so that I can determine at run-time which derived instance should be injected. The factory is registered with the container as a singleton, as are the derived instances.

在用DI容器注册了所有对象之后,我调用Container.Verify()方法来验证我的配置.问题是,此方法的实现会创建在容器中注册的每种类型的实例.这些派生的实例创建起来很昂贵,而且它们的创建也有副作用(它们来自正在更新以使用DI的旧代码).从长远来看,我会摆脱副作用,但从短期来看,这是不可行的.

After registering all of my objects with the DI Container I call the Container.Verify() method to verify my configuration. The problem is, the implementation of this method creates an instance of every type registered with the container. These derived instances are expensive to create and their creation has side-effects (they come from legacy code that is being updated to use DI). Long-term I'll get rid of the side-effects but short term it's not feasable.

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

我想保留Verify()调用(至少对于调试版本而言),但是不能接受Verify()调用实例化的这些实例.此外,它们需要在Singleton Lifestyle中注册,因此我不能只在容器中注册它们.

I want to keep the Verify() call (at the very least for debug builds) but can't accept these instances being instantiated by the Verify() call. Furthermore, they need to be registered with a Singleton Lifestyle so I can't just not register them with the container.

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

One solution I came up with is to not register the derived objects with the container (thereby making them non-verifiable), and then have the (Singleton Lifestyle) Factory implementation cache the first instance it creates. It works, but it's dirty, and if someone explicitly requests an instance of the derived type somewhere else (unlikely) a new instance would be created (because the default Lifestyle is Transient).

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

Here's an example of the class structure I have:

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容器中的注册:

And the registrations with the DI Container:

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.

推荐答案

从您的问题中我了解到您非常了解当前方法的弊端,并且当前正在处理一些无法解决的遗留代码在一次迭代中进行了更改.但是由于其他人也会阅读,因此,我想像往常一样记下注入构造函数应该简单,快速且可靠.

From your question I understand that you are very well aware of the downsides of your current approach, and you are currently dealing with some legacy code that can't be changed in one single iteration. But since others will be reading this as well, I'd like to make my usual note that injection constructors should be simple, fast and reliable.

顺便说一句,回答一个问题:不,没有办法标记要跳过的位置,以便在Simple Injector中进行注册.

With that out of the way, to answer question: No, there is no way to mark registration to be skipped for registration in Simple Injector.

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

All InstanceProducer instances that are created for a container prior to calling Verify() will get verified (unless they are garbage collected before). Typically, InstanceProducer instances are created implicitly for you when you call a Register overload, but you can also create new InstanceProducer instances by calling Lifestyle.CreateProducer. These producers will not be part of any object graph (which means that Simple Injector will not use them to auto-wire other types), and they act as root types. Still however, the container keeps track of those producers and verification will apply to them as well.

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

So the trick here is to trigger the creation of new InstanceProducer instances after the verification process. You can do that either by calling Lifestyle.CreateProducer, or you can do this by resolving an unregistered concrete type, as you are doing in your example. Downside of course of resolving an unregistered type is that it gets resolved as transient by default. There are two ways around this. Either you can cache the instance yourself or you can instruct the container to create that particular type as singleton.

自己进行缓存可能看起来像这样:

Doing the caching yourself might look like this:

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

但是,您自己进行缓存有一个缺点,即您会误诊诊断系统,从而无法发现任何生活方式不匹配给你.因此,更好的选择是覆盖生活方式的选择行为,看起来像:

Caching it yourself however has the downside that that you blind the diagnostic system and make it impossible to spot any Lifestyle mismatches for you. So a better option is to override the lifestyle selection behavior, which looks like:

// 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作为延迟机制:

Another way to solve this is by creating InstanceProducers yourself using the Lifestyle.CreateProducer call. Since you want singletons, you will have to call Lifestyle.Singleton.CreateProducer. Those producers need to be created after the call to verify, so you will still need to use Lazy as delaying mechanism:

// 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);
    }
}

还可以考虑将您的工厂更改为调解人或代理人.工厂通常不是正确的抽象,因为它们通常只会增加复杂性.相反,您可以使代理使用相同的接口,并将调用委派给真实实例(在后台您仍在使用工厂式行为):

Also consider changing your factory to a mediator or proxy. Factories are often not the right abstraction, because they often just increase complexity. Instead you can make a proxy that takes the same interface and delegates the call to the real instance (where you still use factory-like behavior in the background):

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 进行通信,就好像总是存在一个.这样可以使您的应用程序代码更整洁,使用者代码更简单,并使使用者更易于测试.

With this proxy, you can hide from any consumer the fact that there are multiple possible AbstractBase implementations. The consumer can simply communicate with AbstractBase as if there always is exactly one. This can make your application code cleaner, consumer code simpler, and make consumers easier to test.

如果没有代理服务器,您仍然可以考虑使用中介者模式.介体的工作原理与上述代理相同,但区别在于它具有自己的接口.因此,这很像工厂(具有自己的接口),但是区别在于中介者负责调用委托的对象.它不会将实例返回给使用者.

If a proxy is not an option, you can still consider the mediator pattern. A mediator works about the same as the proxy above, but with the difference that it gets its own interface. So it's much like the factory (with its own interface), but with the difference that the mediator is responsible for calling the delegated objects. It will not return an instance to the consumer.

我知道,由于 AbstractBase 的结构,这些解决方案可能不适用于您.如果您有一个胖的基类,并且其中的某些方法是虚拟的,而另一些则没有,那么执行此操作可能会非常困难.这些解决方案通常只能在设计良好(SOLID)的系统中很好地工作.但这实际上就是所有这些.我的经验是,没有SOLID代码,一切都会变得很麻烦.作为软件开发人员,我们的主要工作之一是降低软件的总体拥有成本,而做到这一点的最佳方法之一是将SOLID原理应用于我们的软件.

I'm aware that these solutions might not be applicable to you, because of the structure of the AbstractBase. If you have a fat base class with some methods virtual and others not, it might be quite hard to do this. These solutions typically only work well in a well designed (SOLID) system. But that's actually how it all works. My experience is that without SOLID code, everything gets cumbersome. One of our primary jobs as software developers is to lower total cost of ownership of our software and one of the best ways of doing this is by applying the SOLID principles to our software.

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

One last note though. It seems to me that you are reading some configuration value inside your factory. If this value is defined in the application's configuration file, the value can only be changed by restarting the application (which is something that IIS does for you automatically). If this is such configuration value, you actually don't need all this non-sense at all. You can simply make the following registrations:

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();
    }
}

当然,这使我们再次回到无法验证的最初问题.但是我们可以再次通过代理解决此问题:

Of course this brings us back again to the initial problem of not being able to verify. But we can again solve this with a proxy:

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注入消费者:

And if that's not possible, you can even skip the factory and inject a Func into consumers directly:

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

这篇关于简单注入器:如何跳过容器中对象的验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆