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 */
}
好的,所以我正在寻找有关依赖注入及其使用方法的一些具体技巧。
基本上我有一个使用 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 */
}