Правильно ли передавать зависимости в класс, который не использует его сам, а передает его некоторым внутренним классам? - PullRequest
4 голосов
/ 07 ноября 2011

Меня смущает этот блог: http://ayende.com/blog/124929/your-ctor-says-that-your-code-is-headache-inducing-explanation

Скажем, у меня есть библиотека классов, предоставляющая услуги контроля доступа.Я не хочу использовать какой-либо контейнер IoC в самой библиотеке, чтобы упростить его тестирование (поэтому все вводится и контейнера нет. Разрешите в самой библиотеке).(Служба 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);
    }
}

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

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

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

1 Ответ

5 голосов
/ 07 ноября 2011

Правило о том, что нельзя брать зависимость просто для ее передачи, является правильным, но с точки зрения вашего публичного 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);
    }
}
...