我发现我的构造函数开始看起来像这样:
public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )
随着参数列表的不断增加。既然“Container”是我的依赖注入容器,为什么我不能这样做:
public MyClass(Container con)
每个班级?有什么缺点?如果我这样做,感觉就像我在使用美化的静态。请分享您对 IoC 和依赖注入疯狂的看法。
你是对的,如果你将容器用作服务定位器,它或多或少是一个美化的静态工厂。有很多原因I consider this an anti-pattern(另见我书中的this excerpt)。
构造函数注入的一大好处是它使违反 Single Responsibility Principle 的行为非常明显。
发生这种情况时,就该refactor to Facade Services了。简而言之,创建一个新的、更粗粒度接口,隐藏您当前需要的部分或全部细粒度依赖项之间的交互。
我认为您的类构造函数不应该引用您的 IOC 容器期间。这表示您的类和容器之间存在不必要的依赖关系(IOC 试图避免的依赖类型!)。
MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2)
(接口参数)。这与@derivation 的帖子无关,我将其解释为依赖注入容器不应将自身注入其对象,即MyClass myClass = new MyClass(this)
cs class MyTypeFactory { private readonly IServiceProvier mServices; public MyTypeFactory(IServiceProvier services) => mServices = services; MyType Create(int param) => ActivatorUtilities.CreateInstance<MyType>(mServices, param); }
传入参数的难度不是问题。问题是你的班级做得太多,应该更多地分解。
依赖注入可以作为类变得太大的早期警告,特别是因为传递所有依赖项的痛苦越来越大。
我遇到了一个类似的问题,关于基于构造函数的依赖注入以及传递所有依赖项的复杂程度。
我过去使用的一种方法是使用服务层的应用程序外观模式。这将有一个粗略的 API。如果此服务依赖于存储库,它将使用私有属性的 setter 注入。这需要创建一个抽象工厂并将创建存储库的逻辑移动到一个工厂中。
可以在这里找到带有解释的详细代码
Best practices for IoC in complex service layer
问题 :
1) 具有不断增加的参数列表的构造函数。
2) 如果类是继承的(例如:RepositoryBase
),那么更改构造函数签名会导致派生类发生更改。
解决方案 1
将 IoC Container
传递给构造函数
为什么
不再增加参数列表
构造函数的签名变得简单
为什么不
使您的类与 IoC 容器紧密耦合。 (这会导致问题 1. 您想在使用不同 IoC 容器的其他项目中使用该类。2. 您决定更改 IoC 容器)
使您的类不那么具有描述性。 (你不能真正查看类构造函数并说出它需要什么才能运行。)
类可以访问潜在的所有服务。
解决方案 2
创建一个将所有服务分组并将其传递给构造函数的类
public abstract class EFRepositoryBase
{
public class Dependency
{
public DbContext DbContext { get; }
public IAuditFactory AuditFactory { get; }
public Dependency(
DbContext dbContext,
IAuditFactory auditFactory)
{
DbContext = dbContext;
AuditFactory = auditFactory;
}
}
protected readonly DbContext DbContext;
protected readonly IJobariaAuditFactory auditFactory;
protected EFRepositoryBase(Dependency dependency)
{
DbContext = dependency.DbContext;
auditFactory= dependency.JobariaAuditFactory;
}
}
派生类
public class ApplicationEfRepository : EFRepositoryBase
{
public new class Dependency : EFRepositoryBase.Dependency
{
public IConcreteDependency ConcreteDependency { get; }
public Dependency(
DbContext dbContext,
IAuditFactory auditFactory,
IConcreteDependency concreteDependency)
{
DbContext = dbContext;
AuditFactory = auditFactory;
ConcreteDependency = concreteDependency;
}
}
IConcreteDependency _concreteDependency;
public ApplicationEfRepository(
Dependency dependency)
: base(dependency)
{
_concreteDependency = dependency.ConcreteDependency;
}
}
为什么
向类添加新依赖项不会影响派生类
类与 IoC 容器无关
类是描述性的(就其依赖项而言)。按照惯例,如果您想知道 A 依赖于哪个类,则该信息会在 A.Dependency 中累积
构造函数签名变得简单
为什么不
需要创建额外的类
服务注册变得复杂(需要分别注册每一个X.Dependency)
概念上与传递 IoC Container 相同
..
解决方案 2 只是一个原始的,如果有充分的论据反对它,那么描述性评论将不胜感激
我把这整篇文章读了两遍,我认为人们是根据他们所知道的而不是被问到的来回应的。
JP 的原始问题看起来像是通过发送解析器来构造对象,然后是一堆类,但我们假设这些类/对象本身就是服务,可以注入。如果他们不是呢?
JP,如果您希望利用 DI 并希望获得将注入与上下文数据混合的荣耀,那么这些模式(或所谓的“反模式”)都没有专门解决这个问题。它实际上归结为使用将支持您进行此类努力的软件包。
Container.GetSevice<MyClass>(someObject1, someObject2)
...这种格式很少被支持。我相信编写这种支持的难度,加上与实现相关的糟糕性能,使得它对开源开发人员没有吸引力。
但它应该完成,因为我应该能够为 MyClass'es 创建和注册一个工厂,并且该工厂应该能够接收不是为了传递而被推入“服务”的数据/输入数据。如果“反模式”是关于负面后果的,那么强制存在用于传递数据/模型的人工服务类型肯定是负面的(与您将类包装到容器中的感觉相当。同样的直觉适用)。
不过,有些框架可能会有所帮助,即使它们看起来有点难看。例如,忍者:
Creating an instance using Ninject with additional parameters in the constructor
那是针对 .NET 的,它很受欢迎,但仍然没有应有的干净,但我敢肯定,无论您选择使用哪种语言,都会有一些东西。
注入容器是您最终会后悔的捷径。
过度注入不是问题,它通常是其他结构缺陷的症状,最明显的是关注点分离。这不是一个问题,但可能有很多来源,而使这个问题难以解决的原因是您将不得不处理所有这些问题,有时同时处理(想想解开意大利面条)。
这是需要注意的事项的不完整列表
糟糕的域设计(聚合根的......等)
关注点分离不佳(服务组合、命令、查询)请参阅 CQRS 和事件溯源。
或映射器(小心,这些东西可能会给你带来麻烦)
查看模型和其他 DTO(永远不要重复使用一个,并尽量将它们保持在最低限度!!!!)
这是我使用的方法
public class Hero
{
[Inject]
private IInventory Inventory { get; set; }
[Inject]
private IArmour Armour { get; set; }
[Inject]
protected IWeapon Weapon { get; set; }
[Inject]
private IAction Jump { get; set; }
[Inject]
private IInstanceProvider InstanceProvider { get; set; }
}
这是一个粗略的方法,如何在注入值后执行注入和运行构造函数。这是功能齐全的程序。
public class InjectAttribute : Attribute
{
}
public class TestClass
{
[Inject]
private SomeDependency sd { get; set; }
public TestClass()
{
Console.WriteLine("ctor");
Console.WriteLine(sd);
}
}
public class SomeDependency
{
}
class Program
{
static void Main(string[] args)
{
object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));
// Get all properties with inject tag
List<PropertyInfo> pi = typeof(TestClass)
.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
.Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();
// We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
pi[0].SetValue(tc, new SomeDependency(), null);
// Find the right constructor and Invoke it.
ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
ci.Invoke(tc, null);
}
}
我目前正在从事一个像这样的爱好项目https://github.com/Jokine/ToolProject/tree/Core
您使用的是什么依赖注入框架?您是否尝试过使用基于 setter 的注入来代替?
基于构造函数的注入的好处是对于不使用 DI 框架的 Java 程序员来说它看起来很自然。您需要 5 件事来初始化一个类,然后您的构造函数有 5 个参数。缺点是您已经注意到,当您有很多依赖项时,它会变得笨拙。
使用 Spring,您可以使用 setter 传递所需的值,并且可以使用 @required 注释来强制它们被注入。缺点是您需要将初始化代码从构造函数移动到另一个方法,并在通过使用@PostConstruct 标记注入所有依赖项之后让 Spring 调用该方法。我不确定其他框架,但我认为它们会做类似的事情。
两种方式都有效,这是一个偏好问题。
不定期副业成功案例分享