#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 должна быть внутренним и закрытым классом по соображениям безопасности, поэтому он сам не может быть введен в конструктор, который решил бы проблему.
Таким образом, вы получаете множество зависимостей, введенных через конструктор, хотя это не обязательно означает, что сам класс делает слишком много. В блоге говорится, что это ужасно. Есть ли лучший или более правильный способ справиться с этими тремя требованиями:
- AccessGateKeeper должен быть внутренним и запечатанным.
- Нет IoC в библиотеке классов по причинам тестирования.
- Не хочу использовать внедрение свойств / методов, поскольку оно менее надежное.
Ответ №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. Спасибо за этот подробный ответ! Ваше объяснение имеет смысл и прояснило всю мою путаницу. Предложенное вами решение просто потрясающее. Мне нравится этот шаблон абстрагирования любых зависимостей, которые работают вместе, в свой собственный класс. Я вижу, как я рефакторингую свою кодовую базу на основе этого. Еще раз большое вам спасибо!