Dependency-Inversion-Principal:我应该为接口使用额外的 Project/DLL 吗?
Dependency-Inversion-Principal: Should I use an extra Project/DLL for interfaces?
如果我有一个像这样的分层架构:
Business-Layer -> Data Access Layer,如何正确实现Dependency-Inversion-Principal?
作为主要规定,较低级别 (DAL) 使用的接口应由较高级别定义。但是,如果我在业务层 DLL 中定义接口,我将获得循环依赖。将接口移动到一个单独的 DLL 中供两者使用是个好主意吗?
在我看来 - 是的。那么在一个解决方案中的项目会是这样的(不过只是举例,不知道你的具体情况):
- DependencyInjectionProject.InjectInterfaces(对所有项目的引用)- 该项目包含解决接口依赖关系的逻辑
- Domain.YourBusinessLogic(接口引用)
- Infrastructure.Interfaces
- Infrastructure.Implementations(接口引用)
最后,在您的 end/client 应用程序中,您可以引用 DependencyInjectionProject.InjectInterfaces 来解决业务逻辑中的依赖关系。
一般来说,保持抽象分离没有任何缺点。在多个不同的项目中,它们可以 implemented/extended 多种方式。您将只引用需要依赖项的抽象项目。不一定了解实现。
这种方法有更多的好处,而不是一些小的缺点,比如增加项目数量。
@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.
改变应用程序在内存中的状态。
如果我有一个像这样的分层架构: Business-Layer -> Data Access Layer,如何正确实现Dependency-Inversion-Principal?
作为主要规定,较低级别 (DAL) 使用的接口应由较高级别定义。但是,如果我在业务层 DLL 中定义接口,我将获得循环依赖。将接口移动到一个单独的 DLL 中供两者使用是个好主意吗?
在我看来 - 是的。那么在一个解决方案中的项目会是这样的(不过只是举例,不知道你的具体情况):
- DependencyInjectionProject.InjectInterfaces(对所有项目的引用)- 该项目包含解决接口依赖关系的逻辑
- Domain.YourBusinessLogic(接口引用)
- Infrastructure.Interfaces
- Infrastructure.Implementations(接口引用)
最后,在您的 end/client 应用程序中,您可以引用 DependencyInjectionProject.InjectInterfaces 来解决业务逻辑中的依赖关系。
一般来说,保持抽象分离没有任何缺点。在多个不同的项目中,它们可以 implemented/extended 多种方式。您将只引用需要依赖项的抽象项目。不一定了解实现。 这种方法有更多的好处,而不是一些小的缺点,比如增加项目数量。
@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.
改变应用程序在内存中的状态。