依赖注入避免在启动时加载所有内容

时间:2013-01-21 14:16:47

标签: .net dependency-injection mvp

我正在尝试使用依赖注入和MVP将所需的依赖项注入构造函数中。我遇到的问题是,如果我在根MainWindowPresenter上使用依赖注入,所有它的子演示者及其子演示者,视图和服务都将在启动时加载。因为所有内容都是直接或间接从MainWindowPresenter加载的,这意味着整个应用程序将在启动时加载到内存中。

我知道在.NET中创建所有对象在启动时可能不会花费很多,但我不禁认为这是浪费内存,因为它们永远不会同时被使用。有一些像AboutPresenter或HelpPresenter可能永远不会被使用。我错过了或者这是依赖注入应该如何工作?有办法解决这个问题吗?

我能找到解决这个问题的唯一方法是使用工厂,然后可以在需要时创建子演示者/视图/服务。 e.g:

class HelpFactory : AbstractHelpFactory
{
    public IHelpPresenter Create()
    {
         IHelpService helpService = new ConcreteHelpService();
         IHelpView helpView = new ConcreteHelpView();

         HelpSearchPresenter searchPresenter = HelpSearchFactory.Create();

         return HelpPresenter(helpView, helpService, searchPresenter);
    }
}

这与工厂当时依赖于子工厂基本相同,但至少它们比演示者/视图/服务轻,并且他们不需要在需要之前加载子工厂。

2 个答案:

答案 0 :(得分:2)

有几种可能的解决方案(从一般到特定):

撰写根

确定撰写根This is a (preferably) unique location in an application where modules are composed together这是构建所有依赖项的首选解决方案

抽象工厂

您可以注入类似依赖项的工厂,而不是注入依赖项。这将帮助您推迟创建依赖项并仅解决当前情况所需的依赖项。使用ninject.extension.factory的示例:

kernel.Bind<IDependencyFactory>().ToFactory();

kernel
    .Bind<IDependency>()
    .To<DependencyImpl1>()
    .NamedLikeFactoryMethod((IDependencyFactory f) => f.GetJob());

var abstractFactory = kernel.Get<IDependencyFactory>();

var dependency = abstractFactory.GetJob(); 

public abstract class IDependency { }
public class DependencyImpl1 : IDependency { }

public interface IDependencyFactory
{
    IDependency GetJob();
    Lazy<IDependency> GetLazyJob();
}

这也有助于避免过度注入类,例如构造函数过度注入

聚合服务

不是注入依赖项,而是注入聚合处理例程的服务。阅读更多Refactoring to Aggregate Services

延迟加载

有时,由于在启动期间创建和/或很少使用过于昂贵的原因,有必要推迟依赖项的解析。在这些情况下,可以使用Lazy注入而不是IDependency。 Example using Ninject.Extension.Factory

kernel
    .Bind<Lazy<IDependency>>()
    .To<Lazy<IDependency>>()
    .NamedLikeFactoryMethod((IDependencyFactory f) => f.GetLazyJob());

var abstractFactory = kernel.Get<IDependencyFactory>();

var lazyDependencyUsingFactory = abstractFactory.GetLazyJob();

使用lazy-loading w / o factory:

的示例
kernel
    .Bind<IDependency>()
    .To<DependencyImpl1>();

kernel
    .Bind(typeof (Lazy<>))
    .ToMethod(context =>
            ((ILazyLoader) Activator.CreateInstance(typeof (LazyLoader<>).MakeGenericType(context.GenericArguments),
                                                    new object[] { context.Kernel })).Loader);

var lazyDependency = kernel.Get<Lazy<IDependency>>();

lazyDependency.Dump();
lazyDependency.Value.Dump();

ps :完整样本here

答案 1 :(得分:0)

  

...在.NET中创建所有对象可能不会花费很多......

不幸的是,它可能成本很高,特别是当它们的类型分布在几个组件上时。如果您有类似Composition Root的内容,即通常在应用程序开始时所有DI类型注册都发生的一个地方(并且您没有密切注意注册),那么所有涉及的程序集都将具有由运行时立即加载 ,这可能需要相当长的时间。

我目前正在研究避免这个问题的方法,到目前为止已经找到了以下三个解决方案,它们分为两个层次:(a)程序集(由运行时加载)和(t)类型(对象实例化)。

1。 (a)确保包含DI容器类型注册的方法不直接引用实现类型。

例如,使用Autofac就足以替换:

containerBuilder.RegisterType<Foo>().As<IFoo>();
//                            ^^^       ^^^^
// assemblies for both `IFoo` and `Foo` will be loaded before the method executes.

使用等效的lambda注册:

containerBuilder.Register<IFoo>(c => new Foo(…));
//                        ^^^^
// only assembly for `IFoo` will be loaded, but not the one containing `Foo`!

每当方法执行时,它所直接引用的所有类型都必须可用,这意味着在方法执行开始之前加载它们包含的程序集。由于lambdas被编译成单独的类,因此使用lambda注册会延迟加载Foo的实现程序集,直到第一次调用lambda为止。

2。 (a)将所有必需的接口/合同放在尽可能少的装配中。

(这直接来自上面的内容:)

因为接口类型是DI容器类型注册期间所需的接口类型,所以必须在那里加载。如果只将它们放在几个程序集中,那么运行时只需要在应用程序启动期间(即在组合根目录下)加载它们。由于接口类型没有实现,这些组件可能仍然相当轻量级。

对于实现类型,通常会减轻程序集加载时间的问题,因为应用程序通常不会同时需要所有内容;屏幕A中使用了一些类,屏幕B中使用了一些类,这些类在不同时间显示。因此,您可以将实现类型分散到更多的程序集中,并且不会感觉太糟糕,因为它们的加载时间会随着时间的推移而更均匀地分布。

但是,通过应用于抽象接口的相同推理(见上文),可以决定将屏幕A的所有实现类型打包到一个程序集中,将屏幕B的所有类型打包到另一个程序集中,因为它们可能同时装载。

提示:如果您希望解决方案结构在开发时与部署期间不同,则可以在构建后的流程中集成ILMerge等工具来打包多个程序集合而为一。

第3。 (t)使用工厂,或注入Func<T>Lazy<T>代替T

这是您在注入工厂而不是实际对象时已经考虑过的问题。从本质上讲,它可以解决您的问题,因为这意味着只有当消费者实际需要时,才需要实例化T的某个实例:

class Bar
{
    public Bar(Func<IFoo> getFoo, …) // no `Foo` is instantiated for this.
    {
        this.getFoo = getFoo;
        …
    }

    private readonly Func<IFoo> getFoo;

    void MuchMuchLaterInTime()
    {
        IFoo foo = getFoo(); // only now must a `Foo` become available!
    }
}