在C#中正确执行依赖倒置原则是什么?

时间:2018-09-30 18:21:41

标签: c# dependency-injection

我正在构建密码生成器。我正在尝试应用依赖倒置原则(DIP),但到目前为止,我的解决方案似乎仍然与具体数据结合在一起。

如何解耦PasswordGenerator?所以我不必传递给它

new PasswordRequirementsRepository(new PasswordRequirements{[properties assigned here]}) 

我可以注入一个接口,该接口将被IoC容器使用? 如何在不创建PasswordRequirements实例的情况下将分配给PasswordGenerator属性的数据传递到PasswordRequirementsRepository

在传递不同组的密码要求时,我很挣扎,因为在PasswordGenerator中,我必须传递PasswordRequirementsRepository的具体实例,而不是接口。我想我要实现的目标是将PasswordGenerator与具体的密码要求分离。

IPasswordRequirementsRepository.cs

public interface IPasswordRequirementsRepository
{
    PasswordRequirements GetPasswordRequirements();
}

PasswordRequirementsRepository.cs

public class PasswordRequirementsRepository : IPasswordRequirementsRepository
{
    private readonly PasswordRequirements _requirements;

    public PasswordRequirementsRepository(PasswordRequirements requirements)
    {
        _requirements = requirements;
    }

    public PasswordRequirements GetPasswordRequirements()
    {
        return _requirements;
    }
}

IPasswordGenerator.cs

public interface IPasswordGenerator
{
    string GeneratePassword();
}

PasswordGenerator.cs

public class PasswordGenerator : IPasswordGenerator
{

    private readonly IPasswordRequirementsRepository _repository;

    public PasswordGenerator(IPasswordRequirementsRepository repository)
    {
        _repository = repository;
    }

    public string GeneratePassword()
    {

      PasswordRequirements requirements = _repository.GetPasswordRequirements();

      [password generation logic here]

    }
}

PasswordRequirements.cs

public class PasswordRequirements
{
    public int MaxLength { get; set; }
    public int NoUpper { get; set; }
    public int NoLower { get; set; }
    public int NoNumeric { get; set; }
    public int NoSpecial { get; set; }
}

4 个答案:

答案 0 :(得分:3)

  

如何解耦PasswordGenerator?因此,我不必传递给它,而是可以注入一个接口,以供IoC容器使用?

第一个-派生一个接口:

public class IPasswordRequirements
{
  int MaxLength { get; }
  int NoUpper { get; }
  int NoLower { get; }
  int NoNumeric { get; }
  int NoSpecial { get; }
}

2nd-从接口继承:

public class PasswordRequirements : IPasswordRequirements
{
  public int MaxLength { get; set; }
  public int NoUpper { get; set; }
  public int NoLower { get; set; }
  public int NoNumeric { get; set; }
  public int NoSpecial { get; set; }
}

3rd-更新构造函数:

public class PasswordGenerator : IPasswordGenerator
{
  public PasswordGenerator(IPasswordRequirements passwordRequirements)
  {
  }

就是这样。

请勿在此处使用存储库

我担心的是,您对存储库和DI的理解会导致需要一定时间才能始终使用。我认为您缺少的是实例化依赖关系的代码。尽管存储库可能在其核心中提供了它作为其模式的基础,但这并不是正确的选择,原因有两个:首先,您没有将项目存储在存储库中(也就是说,没有分层的虚拟或物理抽象来包装存储库);其次,您没有提供对多种类型的通用访问,只能提供一种类型。

从根本上讲,存储库唯一有用的是将对象..传递给其他层(SQL,文件系统,Web API)的配置/对象。并非在所有情况下都要求使用存储库来了解有关对象创建方式的任何信息。

选择适合您需求的框架

相反,您需要的是围绕DI构建的框架。对象创建和处理,并具有用于配置框架的接口/配置,以便它可以知道依赖关系以帮助创建依赖对象。我想到了AutoFacNinjectUnity这三个。在每种情况下,都需要以某种方式配置每种类型并使用其模式来创建对象。在许多情况下,这些框架甚至可以完全替换为其他Microsoft框架(例如MVC,具有自己的实例化对象的方法,但可以被其他DI框架替换)。这些框架绝不需要知道如何将这些对象传递给其他层的配置。它可以简单地通过配置作为副产品来实现,但这并不是配置的核心。

例如,使用Autofac,首先要创建构建器,这基本上是创建配置的一种好方法:

var builder = new ContainerBuilder()

然后您注册类型:

builder.RegisterType<PasswordRequirements>.As<IPasswordRequirements>();

创建一个容器来管理对象:从实例化到配置。

var container = builder.Build();

创建一个定义对象生存期的作用域。

using (var scope = new BeginLifetimeScope())
{
  // all objects created by scope which be disposed when the scope is diposed.

  var passwordRequirements = scope.Resolve<IPasswordRequirements>();
}

默认情况下,passwordRequirements将是new PasswordRequirements()。从那里您只需建立必要的依赖关系要求,然后让框架处理其余的需求。

答案 1 :(得分:2)

  

与依赖倒置有关的问题的症结所在

创建PasswordGenerator的实例时。在当前设计中注入IPasswordRequirementsRepository的情况下,传递PasswordRequirements的具体实例是有局限性的,对于真正的依赖反转设计,应避免这种情况。


以下是可能的解决方案:

  1. PasswordRequirements创建一个接口,或者最好为一个抽象类,可以重写该类并根据需要进行注入,当将IPasswordRequirementsRepository注入到{{1 }}

让我们考虑一下抽象类:

PasswordGenerator

使用Ninject DI容器绑定以及命名绑定如下:

public abstract class BasePasswordRequirements
{
    public abstract int MaxLength { get; set; }
    public abstract int NoUpper { get; set; }
    public abstract int NoLower { get; set; }
    public abstract int NoNumeric { get; set; }
    public abstract int NoSpecial { get; set; }
}

public class PasswordRequirements : BasePasswordRequirements
{
    public override int MaxLength { get; set; }
    public override int NoUpper { get; set; }
    public override int NoLower { get; set; }
    public override int NoNumeric { get; set; }
    public override int NoSpecial { get; set; }
}

PasswordRequirementsRepository 如下:

 Kernel.Bind<IPasswordRequirementsRepository>().To<PasswordRequirementsRepository>()
 Kernel.Bind<BasePasswordRequirements>().To<PasswordRequirements>()
  1. 另一种选择是构造函数注入,在这种情况下,public class PasswordRequirementsRepository : IPasswordRequirementsRepository { private readonly BasePasswordRequirements Requirements{get;} public PasswordRequirementsRepository(BasePasswordRequirements requirements) { Requirements = requirements; } public BasePasswordRequirements GetPasswordRequirements() { return Requirements; } } 可能不需要基类或接口,在这种情况下,绑定就像:

    PasswordRequirements

这将调用正确的构造函数,并填充相关值

您还可以考虑将方法1和2结合起来,在其中为Kernel.Bind<IPasswordRequirementsRepository>().To<PasswordRequirementsRepository>() .WithConstructorArgument("requirements", new PasswordRequirements { .... }); 创建基类/接口以及构造函数注入。


对于PasswordRequirements的各种版本,您可能要考虑使用命名绑定,下面是示例,而不是:

PasswordRequirements

插入绑定如下

 public class PasswordRequirementsRepository : IPasswordRequirementsRepository
    {
        private readonly Func<string,BasePasswordRequirements> RequirementsFunc{get;}

        public PasswordRequirementsRepository(Func<string,BasePasswordRequirements> requirementsFunc)
        {
            RequirementsFunc = requirementsFunc;
        }

        public BasePasswordRequirements GetPasswordRequirements(string name="Version1")
        {
            return requirementsFunc(name);
        }
    }

此处可以在运行时传递绑定名称,以调整将要注入的对象,从而在运行时更改行为,从而通过使用 DI框架来实现依赖项反转Ninject,其中有很多灵活的选择

答案 2 :(得分:1)

一个选项可能是使用泛型来抽象出您可能使用的各种密码要求,然后将这些选项传递给GeneratePassword方法,因为这实际上是生成密码的参数。 I.E。

interface IPasswordGenerator<TPasswordRequirements>
{
  string GeneratePassword(TPasswordRequirements reqs);
}

interface IPasswordRequirementRepository<TPasswordRequirements>
{
  TPasswordRequirements GetPasswordRequirements();
}

实施
class DefaultPasswordReqs
{
  public int MaxLength { get; set; }
  // ...
}

class DefaultPasswordGenerator : IPasswordGenerator<DefaultPasswordReqs>
{
  public string GeneratePassword(DefaultPasswordReqs reqs)
  {
    // ... logic specific to DefaultPasswordReqs
  }
}

class InMemoryPasswordRequiremntsRepository<TPasswordRequirements> : 
  IPasswordRequirementRepository<TPasswordRequirements>
{
  private readonly TPasswordRequirements _reqs;

  public InMemoryPasswordRequiremntsRepository(TPasswordRequirements reqs)
  {
    _reqs = reqs;
  }

  public TPasswordRequirements GetPasswordRequirements()
  {
    return _reqs;
  }
}

然后,无论依赖密码生成器的任何代码如何,都使其具有依赖项,该依赖项具有将要使用的特定密码要求类型,并读取该要求并使用这些要求来生成密码。

var requirements = _passwordRequiremntsRepository.GetPasswordRequirements();
var password = _passwordGenerator.GeneratePassword(requirements);

答案 3 :(得分:1)

根据您问题中的代码段,PasswordGenerator的实现与IPasswordRequirementsRepository的实现是分离的,因为它是作为构造函数参数而非特定实现提供的接口。

要将PasswordRequirementsRepositoryPasswordRequirements的特定实现分离,您可以执行以下两项操作之一。

  1. 介绍接口IPasswordRequirements
  2. 使PasswordRequirements抽象。

这两种方法都会使PasswordRequirementsRepository的实现与PasswordRequirements的实现脱钩。

DI容器

  

如何解耦PasswordGenerator?所以我不必通过它   new PasswordRequirementsRepository(new PasswordRequirements{[properties assigned here]})   我可以注入一个接口,该接口将被IoC容器使用?如何在不创建PasswordRequirementsRepository实例的情况下将分配给PasswordRequirements属性的数据传递给PasswordGenerator?

我认为问题的这一部分基于对DI容器的作用的误解。构建容器时,您将注册系统中所需的所有类/接口。这可能看起来像以下内容:

Register<IPasswordRequirements>().To<PasswordRequirements>();
Register<IPasswordRequirementsRepository>().To<PasswordRequirementsRepository>();
Register<IPasswordGenerator>().To<PasswordGenerator>();

注册所有内容后,您可以要求容器为您提供接口实例。就您而言,这将是IPasswordGenerator的实例。该请求通常看起来像这样:

var passwordGenerator = contain.Resolve<IPasswordGenerator>();

通常,您只请求程序的最高组件,因为DI容器知道实例化该组件所依赖的每个类都需要什么。您不会通过手动解析依赖项并将它们注入到构造函数中来创建PasswordGenerator的新实例。这种方法抵消了DI容器的目的。