DI构造函数注入整洁

DI Constructor Injection Neatness

好的,所以我正在寻找有关依赖注入及其使用方法的一些具体技巧。

基本上我有一个使用 Ninject(和 Ninject MVC 包)的 MVC 网站。因此,当我创建 MVC 页面时,我在控制器中使用了构造函数注入。这没关系,但 IMO 有点 'ugly',但我不喜欢的主要事情是必须将所有注入的存储库传递到其他 classes,这似乎有点 OTT 不得不传递像 6 个存储库 - 8 个存储库到一个静态方法或对象构造函数中。

更不用说在我的某些页面上,我不得不处理几乎每个存储库,因此控制器构造器变得庞大而不是最易于管理的。

是否有任何其他选项不会让我的代码变得如此混乱?我真的不想将它们作为单个 'setting' 对象传递,因为这只会将问题转移到不同的代码行。

我也为 console/desktop 应用程序使用相同的 class 库。我喜欢在 class 库中使用 DependencyResolver.Current 的想法,但每个人都说这是一种反模式,应该使用构造函数注入。

也许有一个 MyProjectDIContext class,它有一个字典,我可以在控制器构造函数中填充注入的类型,然后根据需要将上下文传递给所有方法?

我已经在寻找答案了,但我似乎找不到合适的答案。

构造函数注入的一大优点是它使设计和可维护性问题更加明显。当使用构造函数注入时,很容易看到 class 有多少依赖,而没有构造函数注入,class 仍然有相同数量的依赖,但它们被隐藏起来了。

您遇到的问题称为 Constructor Over-injection and it's a design smell, because it indicates that you are violating the Single Responsibility Principle (SRP)。 SRP 指导您 class 保持小型、专注,最重要的是:可维护。

Are there any other options that wont clutter my code up as much?

绝对:缩小 classes。当我们使用 MVC 控制器对某个概念的方法进行分组时,例如 'customer' 或 'order',MVC 控制器通常会变得很大。然而,这意味着控制器是一个不断增长的 class,必须为任何到达的新功能进行更改。这违反了 Open/closed Principle,告诉我们应该努力建立一个系统,我们可以在其中插入新功能而不必触及现有的 classes。

因此,解决方案不是恢复到 Service Locator anti-pattern 或 属性 注入,而是创建更小的 classes 来做一件特定的事情。构造函数注入应该是应用依赖注入的主要方式,即使在 class 库中也是如此。

您的控制器似乎正在将存储库改组到其他 classes。让 Ninject 为您提供那些 classes:

public class Controller
{
    public Controller(IDependencyFactory dependency) { }
}

public interface IDependencyFactory
{
    IDependency CreateDependency();
}

public interface IDependency
{
}

public class Dependency : IDependency
{
    public Dependency() { }
}

public class Program
{
    public static void Main()
    {
        var standardKernel = new StandardKernel();
        standardKernel.Bind<IDependencyFactory>().ToFactory();
        standardKernel.Bind<IDependency>().To<Dependency>();
    }
}

您不必编写由工厂扩展处理的 IDependencyFactory 的实现。您的 Dependency-class 将获得由 Ninject.

注入的依赖项

好吧,我似乎找到了一种看起来不那么臭的方法!

我曾认为,如果您使用 MVC,您将通过构造函数获取存储库,这是解决依赖关系的唯一方法。

在做了一些将可重用代码的部分移动到 IoC 友好接口及其实现之后,我注意到当您在控制器构造函数中创建引用 IMyInterface 时,实现 class 的默认构造函数是 运行 并且您可以从那里提取其他 IoC classes,例如已解析的存储库。

这可能是常识,但它确实使事情变得更加整洁并解决了我遇到的问题。


示例

控制器

public IDefaultTemplateManager DefaultTemplateManager { get; set; }

public DefaultController(IDefaultTemplateManager defaultTemplateManager) {
    this.DefaultTemplateManager = defaultTemplateManager;
}

public ActionResult MyAction(FormCollection collection) {
    DefaultTemplateManager.Process("MyKeyHere");
    View();
}

IDfaultTemplateManager

public interface IDefaultTemplateManager {
    ProcessResponse Process(string UniqueKey, DefaultTemplateManagerEditMode DatabaseName, string DefaultTemplateName);
}

默认模板管理器

    public class DefaultTemplateManager : IDefaultTemplateManager {

        protected IRepository<MyEntity1> MyEntityRepo1 { get; set; }
        protected IRepository<MyEntity2> MyEntityRepo2 { get; set; }
        protected IRepository<MyEntity3> MyEntityRepo3 { get; set; }
        protected IRepository<MyEntity4> MyEntityRepo4 { get; set; }
        protected IRepository<MyEntity5> MyEntityRepo5 { get; set; }
        protected IRepository<MyEntity6> MyEntityRepo6 { get; set; }

        public DefaultTemplateManager(IRepository<MyEntity1> MyEntityRepository1, IRepository<MyEntity2> MyEntityRepository2, IRepository<MyEntity3> MyEntityRepository3, IRepository<MyEntity4> MyEntityRepository4, IRepository<MyEntity5> MyEntityRepository5, IRepository<MyEntity6> MyEntityRepository6, ) {
            this.MyEntityRepo1 = MyEntityRepository1;
            this.MyEntityRepo2 = MyEntityRepository2;
            this.MyEntityRepo3 = MyEntityRepository3;
            this.MyEntityRepo4 = MyEntityRepository4;
            this.MyEntityRepo5 = MyEntityRepository5;
            this.MyEntityRepo6 = MyEntityRepository6;
        }

        public ProcessResponse Process(string UniqueKey) {
            /* Do Work */
        }