C# 如何避免在单向api设计中传递DI容器?
我有一个使用活动记录设计的业务实体的业务层和一个单向api表面。我有两个截然不同的问题: 在不使代码复杂化的情况下,应该如何处理运行时值,例如将id值从DAL传递给构造的对象?这是通过参数覆盖来实现的吗? 如果我没有向下传递容器并使其更像一个反模式/服务定位器,那么如何创建其他业务实体并传递依赖关系 产品是包装容器的根,充当我们的应用程序外观,以及BAL其余部分的入口点。我试图解决的问题在Product.FindCustomer和Customer.FindDocument中 我的目标之一是对域进行建模,并提供一个非常容易理解的API。目前,我们的BAL使用服务定位器,但我正在尝试用合适的IoC/DI和容器替换它C# 如何避免在单向api设计中传递DI容器?,c#,dependency-injection,dependencies,unity-container,C#,Dependency Injection,Dependencies,Unity Container,我有一个使用活动记录设计的业务实体的业务层和一个单向api表面。我有两个截然不同的问题: 在不使代码复杂化的情况下,应该如何处理运行时值,例如将id值从DAL传递给构造的对象?这是通过参数覆盖来实现的吗? 如果我没有向下传递容器并使其更像一个反模式/服务定位器,那么如何创建其他业务实体并传递依赖关系 产品是包装容器的根,充当我们的应用程序外观,以及BAL其余部分的入口点。我试图解决的问题在Product.FindCustomer和Customer.FindDocument中 我的目标之一是对域进
API越深,需要的依赖性就越多,所有更高级别的构造函数都可能相当长,并且可能不再具有内聚性。虽然使用半度量可以将DI压缩到大多数应用程序设计中,但不幸的是,并非所有的应用程序设计都对DI特别友好。当涉及到API设计时,创建智能实体似乎很神奇,但事实是,在它们的核心,它们违反了SRP加载和保存是独立的责任,不管您如何划分它 您基本上有4种选择: 找到一种更有利于DI的设计,并将其对象模型用于API 找到一种更有利于DI的设计,并为您的API创建一个facade对象模型 使用属性注入加载依赖项,并让最终用户控制构造函数 使用服务定位器 在尝试与DI结合使用时,我遇到了类似的问题,经过多次尝试,我最终决定,需要去寻找更好的设计方法的是CSLA 有一段时间,我尝试使用选项3。在这种情况下,您可以围绕DI容器创建一个facade包装器,并且只通过静态访问器公开其构建方法。这将防止将容器用作服务定位器
[Dependency]
public ISomeDependency SomeDepenency { get; set; }
public Customer()
{
Ioc.BuildUp(this);
}
一些DI容器可以使用fluent配置而不是属性注入属性,因此您的业务模型不需要引用容器,但这会使DI配置非常复杂。另一个选择是让构建自己的属性
备选方案1和2类似。基本上,您将每个职责都划分到自己的类中,并将实体分离到哑数据容器中。一种有效的方法是使用
不幸的是,这方面的主要问题是对象生命周期的控制不再取决于DI容器,因此像DbContext这样的依赖项需要特殊处理
另一种变体是将每个实体变成一个简单的对象,该对象使用其他松散耦合的API对象在内部构建自己的DI容器。对于难以与DI一起使用的遗留框架(如web表单),这是推荐的方法
最后,创建一个静态服务定位器,所有API对象都使用它来解析它们的依赖关系。虽然这最能实现目标,但它应该被视为最后的手段。最大的问题是,您无法快速、轻松地理解类所需的依赖关系。因此,您要么被迫创建和更新文档,指明对最终用户的依赖关系,要么最终用户必须通过挖掘源代码才能找到。使用服务定位器是否可以接受取决于您的目标受众,以及您希望他们能够自定义默认之外的依赖关系的频率。如果自定义依赖项是千载难逢的事情,那么它可能会起作用,但是如果25%的用户需要添加自定义依赖项,那么服务定位器可能不是正确的方法
底线是,如果可维护性是您的主要目标,那么选项1显然是赢家。但是,如果您与这个特定的API设计结为夫妻,那么您将需要选择其他选项之一,并接受支持这样一个API所涉及的额外维护
参考资料:
.
public abstract class ActiveRecord<TEntity, TKey>
{
protected ActiveRecord(IDataContext context)
{
}
public virtual void Load() ...
public virtual void Save() ...
public virtual void Delete() ...
}
public abstract class BusinessEntity<TEntity, TKey> : ActiveRecord<TEntity, TKey>
{
protected BusinessEntity(IDataContext context) : base(context)
{
}
protected BusinessEntity(IDataContext context, TKey id) : this(context)
{
}
...
}
var customer = product.FindCustomer("123");
var account = customer.FindAccount("321");
var document = account.FindDocument("some_code");
var file = document.GetFile();
[Dependency]
public ISomeDependency SomeDepenency { get; set; }
public Customer()
{
Ioc.BuildUp(this);
}
public class FindCustomer : IDataQuery<Customer>
{
public string CustomerNumber { get; set; }
}
public class FindCustomerHandler : IQueryHandler<FindCustomer, Customer>
{
private readonly DbContext context;
public FindCustomerHandler(DbContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public Customer Handle(GetCustomer query)
{
return (from customer in context.Customers
where customer.CustomerNumber == query.CustomerNumber
select new Customer
{
Id = customer.Id,
Name = customer.Name,
Addresses = customer.Addresses.Select(a =>
new Address
{
Id = a.Id,
Line1 = a.Line1,
Line2 = a.Line2,
Line3 = a.Line3
})
.OrderBy(x => x.Id)
}).FirstOrDefault();
}
}
var customer = new CustomerBuilder().Build(); // defaults
var customer = new CustomerBuilder(c =>
c.WithSomeDependency(new SomeDependency()).Build(); // overridden dependency