如何在 C# 中使用带有继承的依赖注入

Max*_*nas 15 c# inheritance dependency-injection solid-principles

介绍

大家好,我目前正在使用 C# 开发一个持久性库。在那个库中,我已经实现了存储库模式,我遇到了一个 SOLID 问题。这是我当前实现的一个简化示例,重点关注基本要素:

持久化库中包含的抽象存储库:

public abstract class Repository<T> 
{
    protected Repository(
        IServiceA serviceA,
        IServiceB serviceB) 
    {
        /* ... */
    }
}
Run Code Online (Sandbox Code Playgroud)

库用户创建的具体存储库:

public class FooRepository : Repository<Foo> 
{
    protected FooRepository(
        IServiceA serviceA,
        IServiceB serviceB) :
        base(serviceA, serviceB)
    {
        /* ... */
    }
}
Run Code Online (Sandbox Code Playgroud)

问题

好的,使用当前代码,派生类必须知道基类的每个依赖项都可以,但是如果我向基类添加依赖项怎么办?每个派生类都会中断,因为它们需要将新的依赖项传递给基类......所以目前,我被限制永远不会改变基类构造函数,这是一个问题,因为我希望我的基类有可能发展。这个实现显然打破了开放/封闭原则,但我不知道如何在不打破 SOLID 的情况下解决这个问题......

要求

  • 该库应该易于用户使用
  • 具体的存储库应该能够通过 DI 构建
  • 应将一个或多个依赖项添加到抽象存储库中,而不会影响派生存储库
  • 应该可以像使用控制器的ASP.NET MVC 框架一样使用命名约定在 DI 容器中注册每个存储库
  • 如果他愿意,用户应该能够在他的派生存储库中添加更多依赖项

已经设想的解决方案

1. 服务聚合模式

本文之后,服务聚合器模型可以应用于这种情况,因此代码看起来像这样:

持久化库中包含的抽象存储库:

public abstract class Repository<T> 
{

    public interface IRepositoryDependencies
    {
        IServiceA { get; }
        IServiceB { get; }
    }

    protected Repository(IRepositoryDependencies dependencies) 
    {
        /* ... */
    }
}
Run Code Online (Sandbox Code Playgroud)

库用户创建的具体存储库:

public class FooRepository : Repository<Foo> 
{
    protected Repository(IRepositoryDependencies dependencies) :
        base(dependencies)
    {
        /* ... */
    }
}
Run Code Online (Sandbox Code Playgroud)

优点

  • 如果将依赖项添加到基类,派生类不会中断

缺点

  • IRepositoryDependencies如果我们添加依赖项,则必须修改接口的实现
  • 文章并没有解释如何使用城堡DynamicProxy2(这是一个未知的技术对我来说)来动态生成服务聚合

2. 建造者模式

也许,可以删除基础存储库构造函数并引入构建器模板来创建存储库,但是要使此解决方案起作用,构建器必须是可继承的,以允许用户输入其存储库自己的依赖项。

优点

  • 如果将依赖项添加到基类,派生类不会中断
  • 存储库构建由另一个类管理

缺点

  • 用户必须为他想要创建的每个存储库创建一个构建器
  • 使用命名约定通过 DI 注册每个存储库变得更加困难

3. 属性注入

也许删除基本存储库构造函数并将 DI 配置为使用属性注入可能是一种选择。

优点

  • 如果将依赖项添加到基类,派生类不会中断

缺点

  • 我认为属性设置者必须是公共的?

结论

在 SOLID 世界中是否有任何上述解决方案可以接受?如果没有,你们有什么解决办法吗?非常感谢您的帮助!

Ank*_*jay 6

正如您所要求的,这是通过组合而不是继承来解决此问题的一个非常基本和粗略的示例。

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()
    {
          .
          .
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,这里的抽象基类和每个子存储库类将仅依赖于IRepositoryService或任何其他所需的依赖项(请ISomeOtherService参阅参考资料ChildRepository2)。

这样,您的子存储库仅提供IRepositoryService对基类的依赖项,而您不需要提供IRepositoryService任何地方的依赖项。


Max*_*nas 6

经过多年的经验,我发现装饰模式非常适合这个。

执行:

// 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.");
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

// 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());
Run Code Online (Sandbox Code Playgroud)

这种模式很棒,因为您可以将行为封装在单独的类中,而不会破坏更改,并且仅在您真正需要它们时才使用它们。