如何在C#

时间:2018-01-01 00:15:42

标签: c# inheritance dependency-injection solid-principles

简介

大家好,我目前正在使用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)
    {
        /* ... */
    }
}

问题

好的,使用当前代码,派生类必须知道基类的每个依赖项都可以,但是如果我向基类添加依赖项呢?每个派生类都会中断,因为它们需要将新的依赖项传递给基类...所以目前,我只能从不更改基类构造函数,这是一个问题,因为我想要我的基类有可能进化。 此实现明确打破了开放/封闭原则,但我不知道如何在不破坏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世界中是否有任何可以接受的解决方案?如果没有,你有解决方案吗?非常感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

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

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的依赖关系。

答案 1 :(得分:1)

经过几年的经验,我发现 Decorator Pattern (装饰器模式)是一个完美的解决方案。

实施:

// 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.
IRepositoty<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());

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

答案 2 :(得分:0)

结论&#34;我限制永远不会更改基类构造函数&#34;是纯粹的示范IoC容器的重量&#34;模式&#34;是有毒的。

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

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

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