О DI и SRP
Я пишу следующий класс
public class UserApplication
{
private IUserRepository UserRepository { get; set; }
private IUserEmailerService UserEmailerService { get; set; }
public UserApplication(IUserRepository userRepository, IUserEmailerService userEmailerService)
{
this.UserRepository = userRepository;
this.UserEmailerService = userEmailerService;
}
public bool Authenticate(string login, string pass)
{
// Here I use UserRepository Dependency
}
public bool ResetPassword(string login, string email)
{
// Here I only use both Dependecies
}
public string GetRemeberText(string login, string email)
{
// Here I only use UserRepository Dependency
}
}
Я использую Unity для управления своими экземплярами, поэтому я понял, что использую обе зависимости только для одного метода, поэтому, когда я прошу контейнер предоставить экземпляр для этого класса, обе зависимости внедряются в этот класс, но мне не нужны эти два экземпляры для всех методов, поэтому в Authenticate user мне нужен только репозиторий. Так я ошибаюсь, делая это? Есть ли другой способ, который имеет только зависимость, которую я использую для всех случаев в этом классе?
Я думаю об использовании командного шаблона для этого, поэтому я классифицирую 3 класса с одним методом и только теми зависимостями, которые мне нужны внутри, вот так:
public class AuthenticateUserCommand : ICommand
{
private IUserRepository UserRepository { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public void Execute()
{
// executes the steps to do that
}
}
public class ResetUserPasswordCommand : ICommand
{
private IUserRepository UserRepository { get; set; }
private IUserEmailerService UserEmailerService { get; set; }
public string Login { get; set; }
public string Email { get; set; }
public void Execute()
{
// executes the steps to do that
}
}
3 ответа
Другой подход заключается в создании ролевого интерфейса для каждого поведения. Так что вы бы IUserAuthenticationService
, IUserPasswordResetService
, а также IUserRememberPasswordService
, Интерфейсы могут быть реализованы одним классом, таким как UserApplication
или они могут быть реализованы с отдельными классами для поддержки SRP. Шаблон команды, который вы описываете, имеет аналогичное преимущество для SRP. Одна проблема с шаблоном команды состоит в том, что эти зависимости все еще должны быть предоставлены чем-то. Если зависимости предоставляются контроллером, вам все равно придется сначала получить зависимости от контроллера, и у вас останется проблема, аналогичная вашей в первом примере.
Компромисс в случае специфичного для роли интерфейса, а также в шаблоне команд - это потеря единства. Стоимость этого, безусловно, зависит от предпочтения и перспективы, а также от степени, в которой вы хотите применять SRP. С одной стороны, сплоченность, обеспечиваемая наличием единого поведения, связанного с аутентификацией, может быть полезной. С другой стороны, это может привести к смещению зависимости, как вы описываете.
Так я ошибаюсь, делая это?
Зависимости № 2 - это не конец света, они не делают конструктор раздутым или нечитаемым.
Ранее предоставленные ответы хорошо подходят для случаев, когда у вас есть 3-4+ зависимости.
Вы также можете иногда передавать определенную зависимость в качестве параметра методу, если он используется только в этом методе. В этом смысле вы можете попробовать ввести инъекцию вызова метода Unity, хотя я не уверен, что она была специально предназначена для этой цели.
Я обычно реализую форму шаблона команды, как вы думаете делать. Однако в нем также есть элементы того, что упоминал eulerfx. Я просто называю их задачами. Например:
public interface ITask
{
void Execute();
}
public interface IAuthenticateTask : ITask {}
public interface IResetPasswordTask : ITask {}
Затем я реализую их и ввожу необходимые зависимости. Так что у меня есть ролевые интерфейсы и реализации.
Я бы не стал использовать сервисный локатор, как вы указали в своем бите ответа.
Когда у меня возникает ситуация, когда мне нужен доступ к различным задачам, как в контроллере проекта ASP.NET MVC, я использую внедрение свойств вместо внедрения конструктора. Мне просто нужно, чтобы мой DI-контейнер (я использую замок) требовал определенных инъекций на основе соглашения во время выполнения.
Таким образом, я все еще могу легко тестировать контроллер, так как мне не нужно предоставлять все объекты, в которые инжектор добавлен, а только те свойства, которые мне нужны для теста, с дополнительным преимуществом, что определенные свойства будут по-прежнему требоваться так же, как и при условии по инжектору конструктора.
Обновить:
Есть несколько вариантов, доступных с использованием этого подхода. Основной интерфейс, который может быть интересен для задачи, - это ролевый интерфейс. Унаследованные интерфейсы, такие как ITask
будет только для удобства в простых случаях. Это может быть расширено также с помощью дженериков:
public interface ITask<TInput>
{
void Execute(TInput input);
}
public interface IOutputTask<TOutput>
{
TOutput Execute();
}
public interface IOutputTask<TOutput, TInput>
{
TOutput Execute(TInput input);
}
Еще раз это для удобства:
public interface IAuthenticateTask : IOutputTask<bool> {}
// or
public interface IAuthenticateTask : IOutputTask<AuthenticationResult> {}
Вы можете работать только на уровне ролей:
public interface IAuthenticateTask
{
AuthenticationResult Execute(string username, string password);
}
Нужно только полагаться на специфичный для роли интерфейс для зависимостей.