【发布时间】:2018-06-11 07:13:57
【问题描述】:
简介
大家好,我目前正在使用 C# 开发一个持久性库。在那个库中,我实现了我面临 SOLID 问题的存储库模式。 这是一个简化的示例,我当前的实现重点关注基本要素:
包含在持久性库中的抽象存储库:
public abstract class Repository<T>
{
protected Repository(
IServiceA serviceA,
IServiceB serviceB)
{
/* ... */
}
}
库用户创建的具体存储库:
public class FooRepository : Repository<Foo>
{
protected FooRepository(
IServiceA serviceA,
IServiceB serviceB) :
base(serviceA, serviceB)
{
/* ... */
}
}
问题
好的,使用当前代码,派生类必须知道基类的每个依赖项都可以,但是如果我向基类添加依赖项怎么办?每个派生类都会中断,因为它们需要将新的依赖项传递给基类......所以目前,我仅限于永远不会更改基类构造函数,这是一个问题,因为我想要我的基类有进化的可能。 这个实现明显打破了Open/Closed Principle,但是我不知道如何在不打破SOLID的情况下解决这个问题......
要求
- 该库应该易于用户使用
- 具体的存储库应该能够通过 DI 构建
- 应将一个或多个依赖项添加到抽象存储库,而不影响派生存储库
- 应该可以使用命名约定在 DI 容器中注册每个存储库,就像 ASP.NET MVC 框架 使用控制器
- 如果需要,用户应该能够在他的派生存储库中添加更多依赖项
已经设想的解决方案
1。服务聚合器模式
按照这个article,服务聚合器模型可以应用于这种情况,所以代码看起来像这样:
包含在持久性库中的抽象存储库:
public abstract class Repository<T>
{
public interface IRepositoryDependencies
{
IServiceA { get; }
IServiceB { get; }
}
protected Repository(IRepositoryDependencies dependencies)
{
/* ... */
}
}
库用户创建的具体存储库:
public class FooRepository : Repository<Foo>
{
protected Repository(IRepositoryDependencies dependencies) :
base(dependencies)
{
/* ... */
}
}
优点
- 如果将依赖项添加到基类中,派生类不会中断
缺点
- 如果我们添加依赖项,
IRepositoryDependencies接口的实现必须修改 - article 没有解释如何使用 Castle DynamicProxy2(这对我来说是一个未知的技术)来动态生成服务聚合器
2。建造者模式
也许,可以删除基础存储库构造函数并引入构建器模板来创建存储库,但是要使此解决方案起作用,构建器必须是可继承的,以允许用户输入他的存储库自己的依赖项。
优点
- 如果将依赖项添加到基类中,派生类不会中断
- 存储库构建由另一个类管理
缺点
- 用户必须为他想要创建的每个存储库创建一个构建器
- 使用命名约定通过 DI 注册每个存储库变得越来越困难
3。属性注入
也许删除基础存储库构造函数并将 DI 配置为使用属性注入可能是一种选择。
优点
- 如果将依赖项添加到基类中,派生类不会中断
缺点
- 我认为属性设置器必须是公共的?
结论
在 SOLID 世界中是否有任何上述解决方案可以接受?如果没有,你们有解决方案吗?非常感谢您的帮助!
【问题讨论】:
-
基类的新依赖有什么作用?如果基类需要一个新的依赖项,这表明它将承担额外的责任。 IOW - 如果没有额外的依赖,它会做什么以前无法做到的事情?难道新行为根本不属于那个类?
-
@ScottHannen 例如,如果我想添加一个简单的记录器。另外,我目前有一个通过工作单元注册存储库的依赖项,但是如果将来我不再需要它怎么办?让派生类知道基类的每一个依赖真的可以吗?
-
“这个实现显然打破了开放/封闭原则”我不确定我是否同意你的看法。对我来说,如果您需要对基类进行更改,那么派生类应该传递实例化基类所需的内容。如果派生类不需要依赖并且这是一个完全有效的情况,那么也许你应该创建一个额外的构造函数而不使用依赖。或者创建另一个基类,该基类可以从原始基类派生,并为需要该依赖项的派生类附加依赖项
-
如果你的基类有多个构造函数,那么我作为用户会决定使用哪一个。如果我需要记录器,那么我将使用记录器调用构造函数。您只需要确保实现工作正常,并且该类对每个构造函数版本都很有用。将来,如果您需要添加额外的职责,那么您不应该这样做,因为那样您会破坏 SRP。
-
@AnkitVijay 好的,但是如果构建基类的方式发生了变化呢?可以在不添加或删除其职责的情况下更改类的构建方式,如果发生这种情况,每个派生类都必须更改?如果我添加一个依赖项,我可以添加一个构造函数,我理解这一点,你是完全正确的,但是如果所需的依赖项发生了变化怎么办?我应该永远不要更改所需的依赖项吗?
标签: c# inheritance dependency-injection solid-principles