如何在 C# 中使用带有继承的依赖注入
How to use dependency injection with inheritance in C#
简介
大家好,我目前正在使用 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)
{
/* ... */
}
}
问题
好的,使用当前代码,派生的 class 必须知道基础 class 的每个依赖项,这没问题,但是如果我向基础 [=137 添加依赖项会怎样=]?每个派生 class 都会中断,因为它们需要将新的依赖项传递给基础 class...所以目前,我被限制永远不会更改基础 class constructor 这是个问题,因为我希望我的基础 class 有可能进化。 这个实现显然违反了Open/Closed原则,但我不知道如何在不破坏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)
{
/* ... */
}
}
优点
- 如果将依赖项添加到基础 class
,派生的 classes 不会中断
缺点
- 如果添加依赖,
IRepositoryDependencies
接口的实现必须修改
- article没有解释如何使用Castle DynamicProxy2(对我来说这是一个未知的技术)动态生成服务聚合器
2。建造者模式
或许,可以删除基础存储库构造函数并引入构建器模板来创建存储库,但要使此解决方案起作用,构建器必须是可继承的,以允许用户输入其存储库自己的依赖项。
优点
- 如果将依赖项添加到基础 class
,派生的 classes 不会中断
- 存储库建设由另一个人管理 class
缺点
- 用户必须为他想要创建的每个存储库创建一个构建器
- 使用命名约定通过 DI 注册每个存储库变得更加困难
3。 属性注射
或许删除基本存储库构造函数并配置 DI 以使用 属性 注入可能是一个选项。
优点
- 如果将依赖项添加到基础 class
,派生的 classes 不会中断
缺点
- 我认为 属性 setter 一定是 public?
结论
在 SOLID 世界中是否有任何可以接受的上述解决方案?如果没有,你们有解决办法吗?非常感谢您的帮助!
结论 "I'm limited to never change the base class constructor " 纯粹是证明 IoC 容器 "pattern" 是多么的有毒。
假设您有一个 asp 应用程序,并且希望能够在特定用户的每个会话转到新文件时启用日志记录。用 IoC Containers / Service locators / ASP Controllers constructor 是不可能实现的。你可以做什么:在每次会话开始时,你应该创建这样的记录器并准确地将它传递给所有构造函数(服务实现等越来越深)。没有其他办法。 IoC 容器中没有 "per session" 生命周期之类的东西 - 但这只是实例自然应该存在于 ASP 中的一种方式(意味着 multiuser/multitask 应用程序)。
如果你没有通过构造函数的 DI,你肯定做错了什么(ASP.CORE 和 EF.CORE 是这样 - 不可能看到他们如何用抽象泄漏折磨每个人和他们自己: 你能想象添加自定义记录器会破坏 DbContext https://github.com/aspnet/EntityFrameworkCore/issues/10420 而这是正常的吗?
仅从 DI 配置或动态插件获取(但如果您没有动态插件,请不要考虑任何依赖项 "as it could be a dynamic plugin"),然后执行所有 DI 标准经典方式 - 通过构造函数。
如您所问,这是通过组合而非继承解决此问题的一个非常基本和粗略的示例。
public class RepositoryService : IRepositoryService
{
public RepositoryService (IServiceA serviceA, IServiceB serviceB)
{
/* ... */
}
public void SomeMethod()
{
}
}
public abstract class Repository
{
protected IRepositoryService repositoryService;
public (IRepositoryService repositoryService)
{
this.repositoryService= repositoryService;
}
public virtual void SomeMethod()
{
this.repositoryService.SomeMethod()
.
.
}
}
public class ChildRepository1 : Repository
{
public (IRepositoryService repositoryService) : base (repositoryService)
{
}
public override void SomeMethod()
{
.
.
}
}
public class ChildRepository2 : Repository
{
public (IRepositoryService repositoryService, ISomeOtherService someotherService) : base (repositoryService)
{
.
.
}
public override void SomeMethod()
{
.
.
}
}
现在,这里的抽象基础class和每个子存储库class将只依赖于IRepositoryService
或任何其他必需的依赖项(参考ISomeOtherService
中的[=13] =]).
这样你的子存储库只提供 IRepositoryService
对你的基础 class 的依赖,你不需要在任何地方提供 IRepositoryService
的依赖。
经过几年的经验,我发现 装饰器模式 非常适合这个。
实施:
// 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());
这个模式很棒,因为您可以将行为封装在单独的 类 中,而不会破坏更改,并且仅在您真正需要它们时才使用它们。
可能有点晚了,但是您可以在构造函数中使用 IServiceProvider 代替:
持久化库中包含的抽象仓库:
public abstract class Repository<T>
{
protected Repository(IServiceProvider serviceProvider)
{
serviceA = serviceProvider.GetRequiredService<IServiceA>();
serviceB = serviceProvider.GetRequiredService<IServiceB>();
/* ... */
}
}
库用户创建的具体仓库:
public class FooRepository : Repository<Foo>
{
protected FooRepository(IServiceProvider serviceProvider)
:base(serviceProvider)
{
serviceC = serviceProvider.GetRequiredService<IServiceC>();
serviceD = serviceProvider.GetRequiredService<IServiceD>();
/* ... */
}
}
这样 类 中的每个人都可以在没有任何 cross-dependency 的情况下使用自己的服务。
这种方法有缺点。首先,这是一种带有抽象定位器的服务定位器(反)模式,但这并不意味着它不应该在任何地方使用。对此保持公正,并确保没有更好的解决方案。
其次,没有 compile-time 强制向任何存储库 类 提供必要的服务实现。意思是,如果您不将 IServiceA 的具体实现放入服务提供者中,这仍然会编译。然后,它将失败 run-time。但是,在这种情况下,这是您的要求之一。
简介
大家好,我目前正在使用 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)
{
/* ... */
}
}
问题
好的,使用当前代码,派生的 class 必须知道基础 class 的每个依赖项,这没问题,但是如果我向基础 [=137 添加依赖项会怎样=]?每个派生 class 都会中断,因为它们需要将新的依赖项传递给基础 class...所以目前,我被限制永远不会更改基础 class constructor 这是个问题,因为我希望我的基础 class 有可能进化。 这个实现显然违反了Open/Closed原则,但我不知道如何在不破坏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)
{
/* ... */
}
}
优点
- 如果将依赖项添加到基础 class ,派生的 classes 不会中断
缺点
- 如果添加依赖,
IRepositoryDependencies
接口的实现必须修改 - article没有解释如何使用Castle DynamicProxy2(对我来说这是一个未知的技术)动态生成服务聚合器
2。建造者模式
或许,可以删除基础存储库构造函数并引入构建器模板来创建存储库,但要使此解决方案起作用,构建器必须是可继承的,以允许用户输入其存储库自己的依赖项。
优点
- 如果将依赖项添加到基础 class ,派生的 classes 不会中断
- 存储库建设由另一个人管理 class
缺点
- 用户必须为他想要创建的每个存储库创建一个构建器
- 使用命名约定通过 DI 注册每个存储库变得更加困难
3。 属性注射
或许删除基本存储库构造函数并配置 DI 以使用 属性 注入可能是一个选项。
优点
- 如果将依赖项添加到基础 class ,派生的 classes 不会中断
缺点
- 我认为 属性 setter 一定是 public?
结论
在 SOLID 世界中是否有任何可以接受的上述解决方案?如果没有,你们有解决办法吗?非常感谢您的帮助!
结论 "I'm limited to never change the base class constructor " 纯粹是证明 IoC 容器 "pattern" 是多么的有毒。
假设您有一个 asp 应用程序,并且希望能够在特定用户的每个会话转到新文件时启用日志记录。用 IoC Containers / Service locators / ASP Controllers constructor 是不可能实现的。你可以做什么:在每次会话开始时,你应该创建这样的记录器并准确地将它传递给所有构造函数(服务实现等越来越深)。没有其他办法。 IoC 容器中没有 "per session" 生命周期之类的东西 - 但这只是实例自然应该存在于 ASP 中的一种方式(意味着 multiuser/multitask 应用程序)。
如果你没有通过构造函数的 DI,你肯定做错了什么(ASP.CORE 和 EF.CORE 是这样 - 不可能看到他们如何用抽象泄漏折磨每个人和他们自己: 你能想象添加自定义记录器会破坏 DbContext https://github.com/aspnet/EntityFrameworkCore/issues/10420 而这是正常的吗?
仅从 DI 配置或动态插件获取(但如果您没有动态插件,请不要考虑任何依赖项 "as it could be a dynamic plugin"),然后执行所有 DI 标准经典方式 - 通过构造函数。
如您所问,这是通过组合而非继承解决此问题的一个非常基本和粗略的示例。
public class RepositoryService : IRepositoryService
{
public RepositoryService (IServiceA serviceA, IServiceB serviceB)
{
/* ... */
}
public void SomeMethod()
{
}
}
public abstract class Repository
{
protected IRepositoryService repositoryService;
public (IRepositoryService repositoryService)
{
this.repositoryService= repositoryService;
}
public virtual void SomeMethod()
{
this.repositoryService.SomeMethod()
.
.
}
}
public class ChildRepository1 : Repository
{
public (IRepositoryService repositoryService) : base (repositoryService)
{
}
public override void SomeMethod()
{
.
.
}
}
public class ChildRepository2 : Repository
{
public (IRepositoryService repositoryService, ISomeOtherService someotherService) : base (repositoryService)
{
.
.
}
public override void SomeMethod()
{
.
.
}
}
现在,这里的抽象基础class和每个子存储库class将只依赖于IRepositoryService
或任何其他必需的依赖项(参考ISomeOtherService
中的[=13] =]).
这样你的子存储库只提供 IRepositoryService
对你的基础 class 的依赖,你不需要在任何地方提供 IRepositoryService
的依赖。
经过几年的经验,我发现 装饰器模式 非常适合这个。
实施:
// 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());
这个模式很棒,因为您可以将行为封装在单独的 类 中,而不会破坏更改,并且仅在您真正需要它们时才使用它们。
可能有点晚了,但是您可以在构造函数中使用 IServiceProvider 代替:
持久化库中包含的抽象仓库:
public abstract class Repository<T>
{
protected Repository(IServiceProvider serviceProvider)
{
serviceA = serviceProvider.GetRequiredService<IServiceA>();
serviceB = serviceProvider.GetRequiredService<IServiceB>();
/* ... */
}
}
库用户创建的具体仓库:
public class FooRepository : Repository<Foo>
{
protected FooRepository(IServiceProvider serviceProvider)
:base(serviceProvider)
{
serviceC = serviceProvider.GetRequiredService<IServiceC>();
serviceD = serviceProvider.GetRequiredService<IServiceD>();
/* ... */
}
}
这样 类 中的每个人都可以在没有任何 cross-dependency 的情况下使用自己的服务。
这种方法有缺点。首先,这是一种带有抽象定位器的服务定位器(反)模式,但这并不意味着它不应该在任何地方使用。对此保持公正,并确保没有更好的解决方案。 其次,没有 compile-time 强制向任何存储库 类 提供必要的服务实现。意思是,如果您不将 IServiceA 的具体实现放入服务提供者中,这仍然会编译。然后,它将失败 run-time。但是,在这种情况下,这是您的要求之一。