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) Использование сервисного локатора. Прочитав немало постов, я решил проигнорировать эту опцию.

Плохо ли использовать servicelocation вместо внедрения в конструктор, чтобы избежать загрузки фабричных классов?

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)
    { }
}

Не лучшее решение, но единственное, которое я мог придумать, учитывая ограничение рефакторинга всего приложения.

Другие вопросы по тегам