介绍
大家好,我目前正在使用 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)
{
/* ... */
}
}
问题
好的,使用当前代码,派生类必须知道基类的每个依赖项,这没问题,但是如果我向基类添加依赖项怎么办?每个派生类都会中断,因为它们需要将新的依赖项传递给基类...所以目前,我被限制永远不会更改基类构造函数,这是一个问题,因为我想要我的基类才有进化的可能。 这个实现显然违反了开放/封闭原则,但我不知道如何在不破坏 SOLID 的情况下解决这个问题......
要求
- 库应该易于用户使用
- 应该能够通过 DI 构建具体的存储库
- 应该在不影响派生仓库的情况下将一个或多个依赖项添加到抽象仓库
- 应该可以使用命名约定在 DI 容器中注册每个存储库,就像使用 Controller 的 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
接口(interface)的实现 - article没有解释如何使用 CaSTLe DynamicProxy2(这对我来说是一个未知的技术)来动态生成服务聚合器
2。 builder 模式
也许,可以删除基本存储库构造函数并引入构建器模板来创建存储库,但要使此解决方案起作用,构建器必须是可继承的,以允许用户输入其存储库自己的依赖项。
优点
- 如果将依赖项添加到基类,派生类不会中断
- 存储库构建由另一个类管理
缺点
- 用户必须为他想要创建的每个存储库创建一个构建器
- 使用命名约定通过 DI 注册每个存储库变得更加困难
3。属性注入(inject)
或许删除基本存储库构造函数并将 DI 配置为使用属性注入(inject)可能是一个选项。
优点
- 如果将依赖项添加到基类,派生类不会中断
缺点
- 我认为属性 setter 必须是公开的?
结论
在 SOLID 世界中是否有任何可以接受的上述解决方案?如果没有,你们有解决办法吗?非常感谢您的帮助!
最佳答案
经过多年的经验,我发现装饰器模式非常适合这种情况。
实现:
// Abstract type
public interface IRepository<T>
{
Add(T obj);
}
// Concete type
public class UserRepository : IRepository<User>
{
public UserRepository(/* Specific dependencies */) {}
Add(User obj) { /* [...] */ }
}
// Decorator
public class LoggingRepository<T> : IRepository<T>
{
private readonly IRepository<T> _inner;
public LoggingRepository<T>(IRepository<T> inner) => _inner = inner;
Add(T obj)
{
Console.Log($"Adding {obj}...");
_inner.Add(obj);
Console.Log($"{obj} addded.");
}
}
用法:
// Done using the DI.
IRepository<User> repository =
// Add as many decorators as you want.
new LoggingRepository<User>(
new UserRepository(/* [...] */));
// And here is your add method wrapped with some logging :)
repository.Add(new User());
这种模式很棒,因为您可以将行为封装在单独的类中而不会破坏更改,并且仅在您真正需要它们时才使用它们。
关于c# - 如何在 C# 中使用带有继承的依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48046437/