Как правильно реализовать принцип инверсии зависимостей в C #? - PullRequest
0 голосов
/ 30 сентября 2018

Я создаю генератор паролей.Я пытаюсь применить принцип инверсии зависимостей (DIP), но до сих пор мое решение по-прежнему связано с конкретными данными.

Как отсоединить PasswordGenerator?Поэтому мне не нужно переходить на него

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

, и я могу вместо этого ввести интерфейс, который будет использоваться IoC Container?Как я могу передать данные, присвоенные 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 голосов
/ 01 октября 2018

Как отсоединить Генератор паролей?Поэтому мне не нужно переходить на него, и я могу вместо этого ввести интерфейс, который будет использоваться IoC Container?

1-й - Получить интерфейс:

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

2-й- Наследовать от интерфейса:

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; }
}

3-й - Конструктор обновлений:

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

Вот и все.

Не используйте репозиторий здесь

Я боюсь, что ваше понимание хранилища и DI выводит некоторое время требования, которое всегда должно использоваться вместе.Я считаю, что вам не хватает кода, который создает зависимости.Хотя репозиторий в своей основе может предусматривать, что в качестве основы его шаблона он не является правильным выбором по двум причинам;во-первых, вы не храните элементы в хранилище (то есть нет виртуальной или физической абстракции уровня, чтобы обернуть хранилище), а во-вторых, вы не предоставляете общий доступ к широкому спектру типов, кроме одного.

По своей сути, единственное, что может понадобиться хранилищу - это конфигурация / объект для передачи объектов ... на какой-то другой уровень (SQL, Файловая система, Web API).Хранилище не обязательно во всех случаях знать что-либо о том, как создаются объекты.

Выберите структуру, которая соответствует вашим потребностям

Вместо этого вам нужна структурапостроенный на его ядре вокруг DI;создание и удаление объектов, а также наличие интерфейса / конфигурации для конфигурирования инфраструктуры, чтобы он мог знать зависимости, помогающие в создании зависимых объектов.На ум приходят три AutoFac , Ninject и Unity .В каждом из этих случаев вы в некотором роде обязаны настраивать каждый тип и использовать его шаблон для создания объектов.Во многих случаях эти Frameworks могут даже быть полнофункциональными заменами на другие Microsoft Frameworks (например, MVC имеет свой собственный способ создания объектов, но может быть заменен другими DI Frameworks).Эти платформы никоим образом не должны знать конфигурацию о том, как передавать эти объекты на другие уровни.Это может быть сделано просто путем конфигурации в качестве побочного продукта, но по своей сути это не то, что настроено.

Например, с помощью 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().Оттуда вы просто строите необходимые требования к зависимости и позволяете фреймворку обрабатывать все остальное.

0 голосов
/ 30 сентября 2018

Исходя из фрагмента кода в вашем вопросе, реализация PasswordGenerator отделена от реализации IPasswordRequirementsRepository, поскольку это интерфейс, который задается в качестве аргумента конструктора, а не конкретной реализации.

Чтобы отделить PasswordRequirementsRepository от конкретной реализации PasswordRequirements, вы можете сделать одну из двух вещей.

  1. Представить интерфейс IPasswordRequirements.
  2. Сделать PasswordRequirements абстрактным.

Любой подход будет отделять реализацию PasswordRequirementsRepository от реализациииз PasswordRequirements.

DI-контейнер

Как отсоединить PasswordGenerator?Так что мне не нужно переходить на него new PasswordRequirementsRepository(new PasswordRequirements{[properties assigned here]}), и я могу вместо этого ввести интерфейс, который будет использоваться IoC Container?Как передать данные, назначенные свойствам PasswordRequirements, в PasswordGenerator, не создавая экземпляр PasswordRequirementsRepository?

Я считаю, что эта часть вопроса основана на неправильном понимании роли контейнера DI.При создании вашего контейнера вы зарегистрируете все классы / интерфейсы, которые необходимы в вашей системе.Это может выглядеть примерно так:

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

После регистрации всего, вы можете попросить контейнер предоставить вам экземпляр интерфейса.В вашем случае это будет экземпляр IPasswordGenerator.Запрос обычно выглядит примерно так:

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

Обычно вы запрашиваете только самый верхний компонент вашей программы, так как контейнер DI знает, что нужно для создания экземпляра каждого класса, от которого зависит компонент.Вы не создадите новый экземпляр PasswordGenerator, вручную разрешив зависимости и вставив их в конструктор.Этот подход противодействует назначению контейнера DI.

0 голосов
/ 01 октября 2018

Суть проблемы, связанной с инверсией зависимости

При создании экземпляра PasswordGenerator.который вводит, IPasswordRequirementsRepository, в текущем проекте есть ограничение прохождения конкретного экземпляра PasswordRequirements, которого следует избегать для истинного дизайна инверсии зависимостей.


Ниже приведены возможные решения:

  1. Создайте интерфейс или предпочтительно абстрактный класс для PasswordRequirements, который может быть переопределен и может быть добавлен по необходимости, который будет автоматически введен, когда IPasswordRequirementsRepositoryвставлено в PasswordGenerator

Рассмотрим абстрактный класс:

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; }
}

Использование Ninject DI-контейнер Привязка будет выглядеть следующим образом, наряду с именованной привязкой:

 Kernel.Bind<IPasswordRequirementsRepository>().To<PasswordRequirementsRepository>()
 Kernel.Bind<BasePasswordRequirements>().To<PasswordRequirements>()

PasswordRequirementsRepository будет выглядеть следующим образом:

public class PasswordRequirementsRepository : IPasswordRequirementsRepository
{
    private readonly BasePasswordRequirements Requirements{get;}

    public PasswordRequirementsRepository(BasePasswordRequirements requirements)
    {
        Requirements = requirements;
    }

    public BasePasswordRequirements GetPasswordRequirements()
    {
        return Requirements;
    }
}

Другим вариантом будет конструктор Injection, в этом случае PasswordRequirements, возможно, не потребуется Базовый класс или интерфейс, в этом случае привязка будет выглядеть следующим образом:

Kernel.Bind<IPasswordRequirementsRepository>().To<PasswordRequirementsRepository>()
                     .WithConstructorArgument("requirements", new 
PasswordRequirements { .... });

Это вызвало бы правильный конструктор с соответствующими значениями, заполненными

Вы также можете рассмотреть комбинацию обоих подходов 1 и 2, где вы создаете базовый класс / интерфейс для 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);
        }
    }

Ninject Binding будетследующим образом

    Kernel.Bind<Func<string,BasePasswordRequirements>>()
            .ToMethod(context => name => context.Kernel
                                             .Get<BasePasswordRequirements>(name); 
                           );


    Bind<BasePasswordRequirements>().To<PasswordRequirements>().Named("Version1"); 
    Bind<BasePasswordRequirements>().To<AnotherPasswordRequirements>().Named("Version2"); 

Здесь имя для привязки может быть передано во время выполнения, чтобы настроить объект, который будет введен, и, таким образом, изменить поведение во время выполнения, таким образом, достигая инверсии зависимости с помощью DI framework как Ninject , множество гибких опций

0 голосов
/ 30 сентября 2018

Одним из вариантов может быть использование обобщений для абстрагирования различных типов требований к паролям, которые вы можете использовать, и передача параметров с помощью метода GeneratePassword, поскольку это действительно параметр того, как вы генерируете пароль.IE

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...