MVC3 с IoC: слишком много параметров в базовом контроллере. Любая альтернатива?
Я использую MVC 3. Предположим, у меня есть базовый контроллер и несколько производных контроллеров. Я использую IoC, и мой конструктор базового контроллера выглядит так:
private readonly ICacheProvider _cacheProvider;
private readonly ILoggerProvider _loggerProvider
private readonly IAuditProvider _auditProvider
public abstract class MyControllerBase : Controller
{
protected MyControllerBase(ICacheProvider cacheProvider, ILoggerProvider loggerProvider, IAuditProvider auditProvider, ...)
{
_cacheProvider = cacheProvider;
_loggerProvider = loggerProvider;
_auditProvider = auditProvider;
...
}
}
Все идет нормально? Может быть. Однако каждый из моих производных контроллеров должен определить конструктор, который соответствует сигнатуре конструктора базового класса, например:
public class MyDerivedController1 : MyControllerBase
{
public MyDerivedController1(ICacheProvider cacheProvider, ILoggerProvider loggerProvider, IAuditProvider auditProvider, ...)
: base(cacheProvider, loggerProvider, auditProvider, ...)
{ }
}
И это моя проблема, потому что я должен поддерживать "многословный" конструктор во всех моих производных контроллерах. Если мне нужно добавить нового провайдера, я должен реорганизовать все мои производные контроллеры.
Я думал, что создам класс ServiceProvider (или Service Locator?) (И интерфейс IServiceProvider), который будет иметь конструктор и всех провайдеров в качестве параметров (где IoC будет выполнять свою работу) и выставлять их в качестве свойств. Тогда мой базовый конструктор и производные конструкторы будут иметь только IServiceProvider в качестве параметра.
Однако я обеспокоен тем, что этот подход будет иметь некоторые негативные последствия, например: 1- Скрытая реализация: я не знаю, какого провайдера я использую или нуждаюсь, пока не проверю реализацию. 2. Трудно проверить: когда конструктор содержит параметры, я могу легко его проверить и знаю, чего ожидать (автоматически документируется).
У кого-нибудь есть предложения или комментарии?
1 ответ
После некоторых исследований я не смог найти хорошего решения. Как упомянул Дэниэл Хилгарт, у контроллера слишком много зависимостей, что нарушает SRP. Согласен. Принимая во внимание, что это существующее приложение, я не могу реорганизовать и перепроектировать все это. Например, я хотел бы переместить зависимости, такие как ICacheProvider, ILoggerProvider и IAuditProvider, на другой уровень, который будет отвечать за извлечение данных из хранилища.
Я не хочу начинать новое обсуждение здесь, но мне не нравится идея ссылаться на Entity Framework в моем веб-проекте MVC. Я бы предпочел создать слой доступа к данным и полностью удалить ссылки на EF. Итак, возвращаясь к моей проблеме, у меня есть базовый контроллер с X-зависимостями, и всем производным контроллерам нужен конструктор со всеми этими ссылками (как объяснено в моем вопросе выше).
Я рассмотрел несколько вариантов:
1) Использование сервисного локатора. Прочитав немало постов, я решил проигнорировать эту опцию.
IoC.Resolve vs Конструктор Инъекция
http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
2) Использование агрегатных сервисов. Это хорошая идея, но я не смог сгруппировать свои зависимости и решил тоже проигнорировать эту.
Как избежать безумия Dependency Injection конструктора?
http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/
3) Создание нового провайдера, который выставляет все другие зависимости как свойства. Я не полностью удовлетворен этой опцией, потому что она как-то скрывает детали реализации. Но после всех соображений я решил это реализовать. Найдите ниже новый интерфейс и созданный класс:
public interface IMyControllerProvider
{
ICacheProvider CacheProvider { get; }
ILoggerProvider LoggerProvider { get; }
IAuditProvider AuditProvider { get; }
...
}
public class MyControllerProvider : IMyControllerProvider
{
private readonly ICacheProvider _cacheProvider;
private readonly ILoggerProvider _loggerProvider;
private readonly IAuditProvider _auditProvider;
.....
public MyControllerProvider(ICacheProvider cacheProvider, ILoggerProvider loggerProvider, IAuditProvider auditProvider, ...)
{
_cacheProvider = cacheProvider;
_loggerProvider = loggerProvider;
_auditProvider = auditProvider;
...
}
public ICacheProvider CacheProvider { get { return _context; } }
public ILoggerProvider LoggerProvider { get { return _context; } }
public IAuditProvider AuditProvider { get { return _context; } }
}
После этого я реорганизовал свой базовый контроллер и все производные контроллеры для использования IMyControllerProvider. Это работает нормально, потому что:
1- Производные контроллеры теперь имеют конструктор с одной зависимостью.
2. Хотя новая зависимость IMyControllerProvider "скрывает" реальные зависимости, при тестировании контроллера все еще легко понять, что существуют другие зависимости (конструктор документирует это).
private readonly ICacheProvider _cacheProvider;
private readonly ILoggerProvider _loggerProvider
private readonly IAuditProvider _auditProvider
public abstract class MyControllerBase : Controller
{
protected MyControllerBase(IMyControllerProvider myControllerProvider)
{
_cacheProvider = myControllerProvider.CacheProvider;
_loggerProvider = myControllerProvider.LoggerProvider;
_auditProvider = myControllerProvider.AuditProvider;
}
}
public class MyDerivedController1 : MyControllerBase
{
public MyDerivedController1(IMyControllerProvider myControllerProvider)
: base(myControllerProvider)
{ }
}
Не лучшее решение, но единственное, которое я мог придумать, учитывая ограничение рефакторинга всего приложения.