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

问题描述 投票:2回答:2

Introduction

大家好,我目前正在使用C#中的持久性库。在该库中,我实现了存储库模式,我面临一个SOLID问题。以下是我当前实现的一个简化示例,重点关注基本要点:

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

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

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

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

Problem

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

Requirements

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

Solutions already envisaged

1. Service aggregator pattern

在这个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. Builder pattern

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

优点

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

缺点

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

3. Property injection

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

优点

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

缺点

  • 我认为财产制定者必须公开?

Conclusion

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

c# inheritance dependency-injection solid-principles
2个回答
2
投票

正如你所问,这是一个通过Composition而不是继承来解决这个问题的一个非常基本和粗略的例子。

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()
    {
          .
          .
    }
}

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

这样,您的子存储库仅向您的基类提供IRepositoryService依赖项,您无需在任何地方提供IRepositoryService的依赖项。


0
投票

结论“我仅限于永不改变基类构造函数”,这纯粹证明了IoC容器“模式”的重要性。

想象一下,你有一个asp应用程序,并希望有可能在每次会话转到新文件时为特定用户启用日志记录。使用IoC Containers / Service定位器/ ASP控制器构造函数无法实现。您可以做什么:在每个会话开始时,您应该创建这样的记录器并准确地将其传递给所有构造函数(服务实现等越来越深)。没有别的办法。在IoC容器中没有“每个会话”生命周期这样的东西 - 但这只是实例自然应该存在于ASP中的一种方式(意味着多用户/多任务应用程序)。

如果你没有通过构造函数的DI你肯定做错了什么(这对于ASP.CORE和EF.CORE都是如此 - 不可能看到他们如何折磨每个人和他们自己的抽象泄漏:你能想象添加自定义记录器打破了吗? DbContext https://github.com/aspnet/EntityFrameworkCore/issues/10420这是正常的吗?

从DI配置或动态插件获取(但如果你没有动态插件,不要考虑任何依赖“因为它可能是一个动态插件”)然后做所有DI标准的经典方式 - 通过构造函数。

© www.soinside.com 2019 - 2024. All rights reserved.