【发布时间】:2021-10-27 05:33:35
【问题描述】:
如果我有这样的分层架构: 业务层 -> 数据访问层,如何正确实现依赖反转主体?
作为主体状态,较低级别 (DAL) 使用的接口应由较高级别定义。但是如果我在业务层 DLL 中定义接口,我会得到一个循环依赖。将接口移动到供双方使用的单独 DLL 中是否是个好主意?
【问题讨论】:
标签: c# architecture layer
如果我有这样的分层架构: 业务层 -> 数据访问层,如何正确实现依赖反转主体?
作为主体状态,较低级别 (DAL) 使用的接口应由较高级别定义。但是如果我在业务层 DLL 中定义接口,我会得到一个循环依赖。将接口移动到供双方使用的单独 DLL 中是否是个好主意?
【问题讨论】:
标签: c# architecture layer
在我看来 - 是的。那么解决方案中的项目会是这样的(不过只是一个例子,我不知道你的具体情况):
最后,在您的终端/客户端应用程序中,您可以引用 DependencyInjectionProject.InjectInterfaces 来解决业务逻辑中的依赖关系。
【讨论】:
一般来说,将抽象分开并没有什么坏处。它们可以在多个不同的项目中以多种方式实现/扩展。您只会引用需要依赖项的抽象项目。不一定知道实现。 这种方法有更多的好处,而不是项目数量增加等一些小缺点。
【讨论】:
@ElConrado 没有错,但它有点开放。是的,您可以使用单独的程序集,但您不必这样做; DI 不仅仅是一个程序集/包级别的东西 - 但这是有很多价值的地方,因为它是您可以通过重新部署隔离的 DLL 来交换系统部分的方式,而无需重新部署(或编译)整个东西。
通常我有一个Common 程序集/命名空间,并在其中有我的 DTO 和接口,因为大多数时候我正在处理的应用程序的大小和复杂性都很好。您可以将抽象接口放在单独的程序集中,但我看不出这会给您带来什么实际价值。
就如何在 C# 中做到这一点而言:
程序集:MyApp.Common,命名空间:MyApp.Common.Utilities
// Utility that instantiates concrete providers
// (classes that implement the abstractions / interfaces)
// like IContentDataProvider.
public class ProviderLoader
{
public static object CreateInstance(string fullTypeNameConfigKey)
{
...
}
public static object CreateInstance(string fullTypeName)
{
...
}
}
程序集:MyApp.Common,命名空间:MyApp.Common.Interfaces.IDataProvider
// Defines abstract data access methods
public interface IContentDataProvider
{
ContentInfo Content_SELECT_ByContentID(int contentId);
}
程序集:MyApp.Common,命名空间:MyApp.Common.Info
// Dumb DTO's / POCO's, used by the interfaces / abstractions
// as a way of exchanging data between layers, whilst still
// maintaining a clean separation.
public class ContentInfo
{
...
}
程序集:MyApp.SQLDataProvider,命名空间:MyApp.SQLDataProvider
参考 MyApp.Common
// Concrete data provider
public class ContentDataProvider : IContentDataProvider
{
public ContentInfo Content_SELECT_ByContentID(int contentId)
{
...
}
}
现在是有趣的部分,将它们组合在一起:
程序集:MyApp.Business,命名空间:MyApp.Business
参考 MyApp.Common
// Concrete data provider
public class ContentPublisher
{
// Not strictly necessary, but Lazy-Load is one
// way to instantiate the provider where you need it.
private static MyApp.Common.Interfaces.IDataProvider.IContentDataProvider _iContentDataProvider;
private static MyApp.Common.Interfaces.IDataProvider.IContentDataProvider IContentDataProvider
{
get
{
if (_iContentDataProvider== null)
{
// Here the type of concrete provider would
// be loaded based on a value set in a config file
// so the parameter here would be a config-key.
// But you could use other approaches.
_iContentDataProvider= MyApp.Common.Utilities.ProviderLoader.CreateInstance(...) as MyApp.Common.Interfaces.IDataProvider.IContentDataProvider;
}
return _iContentDataProvider;
}
}
// Use the IContentDataProvider to access the concrete implementation:
public static ContentItem GetContentItemById(int contentId)
{
ContentInfo contentInfo = IContentDataProvider.Content_SELECT_ByContentID(contentId);
...
// Take the lowly ContentInfo DTO and use it,
// sprinkled with some other Business Layer magic,
// to return ContentItem.
}
}
虽然看起来有点矫枉过正,但您没有理由不采用相同的方法(使用接口、提供程序加载器等)并将其全部放在一个程序集中,并让您的应用程序的用户利用它 - 例如选择用于某种目的的算法。他们可以通过配置设置来做到这一点,甚至可以通过 UI 更改应用程序在内存中的状态。
【讨论】: