【问题标题】:How can one use an existing instance to select a type to create in an IoC container如何使用现有实例选择要在 IoC 容器中创建的类型
【发布时间】:2011-09-17 04:27:56
【问题描述】:

这可能只是一个新手问题,但我有以下问题:

public class FooSettings {}
public class BarSettings {}
public class DohSettings {}
// There might be many more settings types...

public interface IProcessor { ... }

public class FooProcessor
    : IProcessor
{
     public FooProcessor(FooSettings) { ... }
}

public class BarProcessor
    : IProcessor
{
     public BarProcessor(BarSettings) { ... }
}

public class DohProcessor
    : IProcessor
{
     public DohProcessor(DohSettings) { ... }
}

// There might be many more processor types with matching settings...

public interface IProcessorConsumer {}

public class ProcessorConsumer 
    : IProcessorConsumer
{
     public ProcessorConsumer(IProcessor processor) { ... }
}

FooSettings 或 BarSettings 的实例是从外部来源提供的,即:

object settings = GetSettings();

现在我想基于注入现有设置实例来解析 ProcessorConsumer,例如:

container.RegisterAssemblyTypes(...); // Or similar
container.Inject(settings);
var consumer = container.Resolve<IProcessorConsumer>();

也就是说,如果提供了 FooSettings 的实例,则会创建一个 FooProcessor 并将其注入到 ProcessorConsumer 中,然后解析该实例。

我无法弄清楚如何在 StructureMap、Ninject 或 Autofac 中执行此操作...可能是因为我是 IoC 容器的新手。因此,非常感谢所有这些或其他容器的答案,以便它们可以进行比较。

更新:我正在寻找一种可以轻松添加新设置和处理器的解决方案。此外,还有从设置类型到处理器类型的一对一映射。但这也允许基于其构造函数参数将其他实例/服务注入给定的处理器类型。 IE。某些处理器可能需要 IResourceProvider 服务或类似服务。这里只是一个例子。

理想情况下,我想要类似的东西

container.For<IProcessor>.InjectConstructorParameter(settings)

或类似的。从而引导IoC容器使用与注入的构造函数参数实例相匹配的处理器类型。

【问题讨论】:

  • 应该public Foo(FooSettings) { ... }public FooProcessor(FooSettings) { }public Foo(BarSettings) { ... }public BarProcessor(BarSettings) { }
  • 可能重复的问题 - stackoverflow.com/questions/6373122/…

标签: c# ioc-container ninject structuremap autofac


【解决方案1】:

您不希望为此进行依赖注入。你想要一个工厂(当然,你可以使用你的容器来构建它)。工厂会知道如何获取 IProcessorSettings 并返回相应的 IProcessor。简而言之,您可以构建一个工厂,使用实现IProcessorSettings 的对象的具体类型和容器来解析适当类型的实例。

【讨论】:

  • 好吧,这似乎需要做很多工作才能实现。您能否详细说明为什么我不希望为此进行依赖注入?另外我想有人可以说:container.For.InjectDependency(settings) 更具体。
  • 工厂+1。这里的问题是 IoC 容器实际上无法知道要创建哪种类型的处理器。在这种情况下,这不是它的工作。工厂将根据适合做出此决定的条件为您处理此问题。
  • 你们中的任何人都可以举一个例子来说明如何做到这一点吗?我不确定我是否理解...
【解决方案2】:

现在,我并不是说这是正确的方法。但是,如果您使用 Autofac,它可能是另一种选择。这假设您很高兴注册代表在您尝试解决 IProcessorConsumer 时致电 GetSettings。如果你能够做到这一点,你可以在注册中做你想做的事情,如下所示:

var cb = new ConatainerBuilder();
cb.Register(c => 
{
    var settings = GetSettings();
    if(settings is FooSettings)
        return new FooProcessor((FooSettings)settings);
    else if(settings is BarSettings)
        return new BarProcessor((BarSettings)settings);
    else
       throw new NotImplementedException("Hmmm. Got some new fangled settings.");

}).As<IProcessor>();

//Also need to register IProcessorConsumer

注意:此代码可能有误,因为我现在无法尝试。

【讨论】:

  • 这行得通,但是你基本上可以只为处理器编写一个工厂并将处理器实例注入容器,但我不想为处理器创建一个“切换”工厂并想允许某些处理器需要更多服务等。我想我希望设置实例充当处理器的选择器,但必须创建一个看起来多余的工厂。
【解决方案3】:

StructureMap 容器公开了Model 属性,允许您查询它包含的实例。

var container = new Container(x =>
{
    x.For<IProcessorConsumer>().Use<ProcessorConsumer>();
    x.For<IProcessor>().Use(context =>
    {
        var model = context.GetInstance<IContainer>().Model;
        if (model.PluginTypes.Any(t => typeof(FooSettings).Equals(t.PluginType)))
        {
            return context.GetInstance<FooProcessor>();
        }
        return context.GetInstance<BarProcessor>();
    });
});

【讨论】:

  • 我的回答显示了如何做问题的机制。但是,我不会考虑 IoC 容器的这种正常用法。您可能想问另一个描述您的情况的问题并征求设计建议。例如,说明在什么情况下您会使用 FooSettings 和 BarSettings?
  • 首先感谢您的回答,好方法似乎是耦合的,如果引入新设置,例如其他设置。基本上,我想创建一个基于使用现有注入实例的对象图,这些实例例如从磁盘加载,即设置。但是设置通常映射到单个特定类型。
  • 我想要得到的是,您可能不想根据容器中是否存在设置类型来做出决定 - 而是要根据之前发生的任何逻辑来确定哪些设置注入。您的应用程序何时以及是否知道是否创建 FooSettings 而不是 BarSettings?
  • 约书亚和我正在寻找同样的东西(请参阅我建议的答案 - 尤其是最后的 cmets)。知道是创建 FooSettings 还是 BarSettings 还是 xxxSettings 的实体是应该负责构造正确类型的实体。查看 GRASP 模式中的“创建者”模式以了解我们的意思。 en.wikipedia.org/wiki/GRASP_(object-oriented_design)#Creator
【解决方案4】:

在 Autofac 中给出:

public class AcceptsTypeConstructorFinder
    : IConstructorFinder
{
    private readonly Type m_typeToAccept;
    public AcceptsTypeConstructorFinder(Type typeToAccept)
    {
        if (typeToAccept == null) { throw new ArgumentNullException("typeToAccept"); }
        m_typeToAccept = typeToAccept;
    }

    public IEnumerable<ConstructorInfo> FindConstructors(Type targetType)
    {
        return targetType.GetConstructors()
            .Where(constructorInfo => constructorInfo.GetParameters()
                .Select(parameterInfo => parameterInfo.ParameterType)
                .Contains(m_typeToAccept));
    }
}

以下作品:

        // Load
        var settings = new BarSettings();
        var expectedProcessorType = typeof(BarProcessor);

        // Register
        var constructorFinder = new AcceptsTypeConstructorFinder(settings.GetType());
        var builder = new ContainerBuilder();
        var assembly = Assembly.GetExecutingAssembly();

        builder.RegisterInstance(settings);

        builder.RegisterAssemblyTypes(assembly)
               .Where(type => type.IsAssignableTo<IProcessor>() && constructorFinder.FindConstructors(type).Any())
               .As<IProcessor>();

        builder.RegisterAssemblyTypes(assembly)
               .As<IProcessorConsumer>();

        using (var container = builder.Build())
        {
            // Resolve
            var processorConsumer = container.Resolve<IProcessorConsumer>();

            Assert.IsInstanceOfType(processorConsumer, typeof(ProcessorConsumer));
            Assert.IsInstanceOfType(processorConsumer.Processor, expectedProcessorType);

            // Run
            // TODO
        }

但是,我发现这相当麻烦,并希望在 IoC 容器中内置更多内容。

【讨论】:

  • 我认为您不会在 IoC 容器中找到更多内置的东西,因为这不是它们的设计目的。 IoC 容器所做的是您要求一个类型,它会解决所有必要的依赖关系。你想要的是提供一个依赖并让它创建一些依赖它的类型。 这与 IoC 容器通常所做的完全相反。
【解决方案5】:

这里尽可能接近正确的工厂方法。但也有一些问题。首先,这是代码;那我们再说吧。

public class FooSettings
{
    public int FooNumber { get; set; }
    public string FooString { get; set; }
}

public class BarSettings
{
    public int BarNumber { get; set; }
    public string BarString { get; set; }
}


public interface IProcessor
{
    void Process();
}

public class FooProcessor : IProcessor
{
    public FooProcessor(FooSettings settings) { }

    public void Process() { }
}

public class BarProcessor : IProcessor
{
    public BarProcessor(BarSettings settings) { }

    public void Process() { }
}


public interface IProcessorFactory
{
    IProcessor GetProcessor(object settings);
}

public interface IProcessorConsumer { }

public class ProcessorConsumer : IProcessorConsumer
{
    private IProcessorFactory _processorFactory;
    private object _settings;

    public ProcessorConsumer(IProcessorFactory processorFactory, object settings)
    {
        _processorFactory = processorFactory;
        _settings = settings;
    }


    public void MyLogic()
    {
        IProcessor processor = _processorFactory.GetProcessor(_settings);

        processor.Process();
    }
}


public class ExampleProcessorFactory : IProcessorFactory
{
    public IProcessor GetProcessor(object settings)
    {
        IProcessor p = null;

        if (settings is BarSettings)
        {
            p = new BarProcessor(settings as BarSettings);
        }
        else if (settings is FooSettings)
        {
            p = new FooProcessor(settings as FooSettings);
        }

        return p;
    }
}

那么问题是什么?这是您为工厂方法提供的不同类型的设置。有时是 FooSettings,有时是 BarSettings。稍后,它可能是 xxxSettings。每个新类型都会强制重新编译。如果你有一个通用的设置类,情况就不是这样了。

另一个问题?您的消费者通过了工厂和设置,并使用它来获得正确的处理器。如果有人将这些传递给您的消费者,只需让该实体在工厂上调用 GetProcessor 并将生成的 IProcessor 传递给消费者。

【讨论】:

  • 通用设置类是一个选项,是我们在某些情况下使用的东西,我试图弄清楚如何使用 IoC 容器来解决。但是,我正在寻找一个没有硬编码 if 语句的解决方案。此外,我不希望在 ProcessorConsumer 中有工厂。例如,创建处理器可能很耗时,因此它应该由 IoC 创建并注入。这也是一个糟糕的解决方案,您不妨在设置 IoC 容器时编写 if 语句。
【解决方案6】:

我认为您正在寻找的是 StructureMap 中的 ForObject() 方法。它可以基于给定的对象实例关闭一个开放的泛型类型。您需要对设计进行的关键更改是引入泛型类型:

public interface IProcessor { }
public interface IProcessor<TSettings> : IProcessor{}

所有重要的东西仍然在IProcessor 上声明,通用的IProcessor&lt;TSettings&gt; 实际上只是一个标记接口。然后,您的每个处理器都将实现通用接口,以声明它们期望的设置类型:

public class FooProcessor : IProcessor<FooSettings>
{
     public FooProcessor(FooSettings settings) {  }
}

public class BarProcessor : IProcessor<BarSettings>
{
     public BarProcessor(BarSettings settings) {  }
}

public class DohProcessor : IProcessor<DohSettings>
{
     public DohProcessor(DohSettings settings) {  }
}

现在,给定一个设置对象的实例,您可以检索正确的IProcessor

IProcessor processor = container.ForObject(settings).
  GetClosedTypeOf(typeof(IProcessor<>)).
  As<IProcessor>();

现在您可以告诉 StructureMap 在解析IProcessor 时使用此逻辑:

var container = new Container(x =>
{
    x.Scan(scan =>
    {
        scan.TheCallingAssembly();
        scan.WithDefaultConventions();
        scan.ConnectImplementationsToTypesClosing(typeof(IProcessor<>));
    });

    x.For<IProcessor>().Use(context =>
    {
        // Get the settings object somehow - I'll assume an ISettingsSource
        var settings = context.GetInstance<ISettingsSource>().GetSettings();
        // Need access to full container, since context interface does not expose ForObject
        var me = context.GetInstance<IContainer>();
        // Get the correct IProcessor based on the settings object
        return me.ForObject(settings).
            GetClosedTypeOf(typeof (IProcessor<>)).
            As<IProcessor>();
    });

});

【讨论】:

  • 是的,可以,但也会增加摩擦。必须为我指定具有设置类型的标记听起来像是不必要的摩擦。但是,我想这有一些好处,因为在相关类型的接口中说明了依赖关系。
  • 您试图将两种类型联系在一起 - 设置类型和处理器类型。所以你不可避免地需要某种方式来声明这种关系。您可能会想出一个自定义约定来扫描 IProcessor 实现并检查其构造函数参数的设置类型,然后存储该信息以供容器以后使用 - 但我敢打赌这将比标记更多的工作界面。
【解决方案7】:

我认为问题在于您实际上并没有以任何结构化的方式指定如何从设置实例中解析处理器。如何让设置对象返回处理器?这似乎是放置这些信息的正确位置:

public interface ISettings
{
   IProcessor GetProcessor();
}

每个实现都必须解析自己的处理器实现:

public class FooSettings : ISettings
{
   //this is where you are linking the settings type to its processor type
   public IProcessor GetProcessor() { return new FooProcessor(this); }
}

任何需要处理器的代码都从设置对象中获取它,您可以从消费者构造函数中引用它:

var consumer = new ProcessorConsumer(Settings.GetProcessor());

【讨论】:

  • 从我的回复中可能很明显,但我不认为 IoC 容器是解决问题的方法(不是说它不能完成,而是它让信息有很长的路要走远离相关类型)。
  • 我想我在给定处理器的构造函数中非常清楚地指定了它。为什么我要将设置绑定到给定的处理器,而不是依赖于设置的处理器?
  • 您的规范要求您在询问之前知道答案。您需要能够从设置中找到处理器,而不是相反。
猜你喜欢
  • 2015-08-05
  • 2013-06-07
  • 2018-05-24
  • 1970-01-01
  • 1970-01-01
  • 2012-10-07
  • 2015-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多