Правильно ли передавать зависимости в класс, который сам их не использует, но передает некоторым внутренне используемым классам

#design-patterns #dependency-injection #constructor

#шаблоны проектирования #внедрение зависимостей #конструктор

Вопрос:

Именно этот блог меня действительно смутил:http://ayende.com/blog/124929/your-ctor-says-that-your-code-is-headache-inducing-explanation

Допустим, у меня есть библиотека классов, предоставляющая службы контроля доступа. Я не хочу использовать какой-либо контейнер IoC в самой библиотеке, чтобы упростить его тестирование (поэтому все вводится, а в самой библиотеке нет container.Resolve). (Служба WCF и другие веб-сайты, использующие эту библиотеку, будут использовать некоторый контейнер для внедрения зависимостей в конструктор.)

Скажем, мой базовый класс выглядит так:

 public class UserAccessManagement
{
    private readonly IUserRepository _repo;
    private readonly IHashProvider _hash;
    private readonly ITokenEncryptor _tokenEnc;

    public UserAccessManagement(IUserRepository repo, IHashProvider hash, ITokenEncryptor tokenEnc)
    {
        _repo = repo; _hash = hash; _tokenEnc = tokenEnc;
    }

    public void GrantAccess(string username, string resource)
    {
        User user = repo.FindUser(username);
        new AccessGateKeeper(user.SpnTicket, _hash, _tokenEnc)
            .GrantAccess(resource);
    }
}
  

Как вы можете видеть, хэш и tokenEnc на самом деле не используются классом, но должны быть переданы конструктору, потому что внутренне он должен передать их хранителю ворот. Конкретная реализация gate keeper должна быть внутренним и закрытым классом по соображениям безопасности, поэтому он сам не может быть введен в конструктор, который решил бы проблему.

Таким образом, вы получаете множество зависимостей, введенных через конструктор, хотя это не обязательно означает, что сам класс делает слишком много. В блоге говорится, что это ужасно. Есть ли лучший или более правильный способ справиться с этими тремя требованиями:

  1. AccessGateKeeper должен быть внутренним и запечатанным.
  2. Нет IoC в библиотеке классов по причинам тестирования.
  3. Не хочу использовать внедрение свойств / методов, поскольку оно менее надежное.

Ответ №1:

Правило о том, чтобы не использовать зависимость только для ее передачи, верно, но с точки зрения вашего общедоступного API это тоже не то, что вы делаете. Поскольку AccessGateKeeper является внутренним, он не является частью общедоступного API — это деталь реализации. С таким же успехом это могла быть частная вспомогательная функция UserAccessManagement класса.

Таким образом, с общедоступной точки зрения, UserAccessManagement класс не принимает зависимость «просто для ее передачи». Вместо этого вы могли бы сказать, что UserAccessManagement классу требуются зависимости IUserRepository , IHashProvider и ITokenEncryptor для выполнения своей работы.

Однако с точки зрения ремонтопригодности текущая реализация тесно связана с UserAccessManagement и AccessGateKeeper , что может быть проблемой, а может и не быть. Однако, если это является проблемой, вы могли бы рассмотреть возможность переноса желаемого поведения в интерфейс. Механическое извлечение может выглядеть следующим образом:

 public interface IAccess
{
    void Grant(object ticket, string resource)
}
  

С помощью этого интерфейса вы могли бы изменить реализацию UserAccessManagement на:

 public class UserAccessManagement
{
    private readonly IUserRepository _repo;
    private readonly IAccess _access;

    public UserAccessManagement(IUserRepository repo, IAccess access)
    {
        _repo = repo;
        _access = access;
    }

    public void GrantAccess(string username, string resource)
    {
        User user = repo.FindUser(username);
        _access.Grant(user.SpnTicket, resource);
    }
}
  

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

 public class Access : IAccess
{
    private readonly IHashProvider _hash;
    private readonly ITokenEncryptor _tokenEnc;

    public Access(IHashProvider _hash, ITokenEncryptor _tokenEnc)
    {
        _hash = hash;
        _tokenEnc = tokenEnc;
    }

    public void Grant(object ticket, string resource)
    {
        new AccessGateKeeper(ticket, _hash, _tokenEnc)
            .GrantAccess(resource);
    }
}
  

Комментарии:

1. Спасибо за этот подробный ответ! Ваше объяснение имеет смысл и прояснило всю мою путаницу. Предложенное вами решение просто потрясающее. Мне нравится этот шаблон абстрагирования любых зависимостей, которые работают вместе, в свой собственный класс. Я вижу, как я рефакторингую свою кодовую базу на основе этого. Еще раз большое вам спасибо!