Как уменьшить количество внедряемых зависимостей от контроллера

Я использую MVC3, Entity Framework v4.3 Code First и SimpleInjector. У меня есть несколько простых классов, которые выглядят так:

public class SomeThing
{
    public int Id { get; set; }
    public string Name { get; set; }
}

У меня есть еще один объект, который выглядит так:

public class MainClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual AThing AThingy { get; set; }
    public virtual BThing BThingy { get; set; }
    public virtual CThing CThingy { get; set; }
    public virtual DThing DThingy { get; set; }
    public virtual EThing EThingy { get; set; }
}

Каждая вещь (в настоящее время) имеет свой собственный класс менеджера, например:

public class SomeThingManager
{
    private readonly IMyRepository<SomeThing> MyRepository;

    public SomeThingManager(IMyRepository<SomeThing> myRepository)
    {
        MyRepository = myRepository;
    }
} 

Следовательно, мой MainController выглядит следующим образом:

public class MainController
{
    private readonly IMainManager MainManager;
    private readonly IAThingManager AThingManager;
    private readonly IBThingManager BThingManager;
    private readonly ICThingManager CThingManager;
    private readonly IDThingManager DThingManager;
    private readonly IEThingManager EThingManager;

    public MainController(IMainManager mainManager, IAThingManager aThingManager, IBThingManager bThingManager, ICThingManager cThingManager, IDThingManager dThingManager, IEThingManager eThingManager)
    {
        MainManager = mainManager;
        AThingManager = aThingManager;
        BThingManager = bThingManager;
        CThingManager = cThingManager;
        DThingManager = dThingManager;
        EThingManager = eThingManager;
    }

    ...various ActionMethods...
}

На самом деле в этом контроллере внедренных зависимостей в два раза больше. Это пахнет. Запах еще хуже, когда вы также знаете, что существует OtherController со всеми или большинством таких же зависимостей. Я хочу провести рефакторинг.

Я уже достаточно знаю о внедрении зависимостей, чтобы понять, что внедрение свойств и поиск сервисов — плохие идеи.

Я не могу разделить свой MainController, потому что это один экран, который требует, чтобы все эти вещи отображались и редактировались одним нажатием одной кнопки «Сохранить». Другими словами, один метод действия с публикацией сохраняет все (хотя я готов изменить это, если это имеет смысл, если это все еще одна кнопка «Сохранить»). Этот экран создан с помощью Knockoutjs и сохраняется с сообщениями Ajax, если это имеет значение.

Я с юмором использовал Ambient Context, но я не уверен, что это правильный путь. Я также с юмором использовал внедрение Фасада. Мне также интересно, следует ли мне реализовать командную архитектуру на этом этапе. (Разве все вышеперечисленное не просто перемещает запах куда-то еще?)

Наконец, и, возможно, независимо от трех вышеперечисленных подходов, следует ли вместо этого иметь один, скажем, LookupManager с явными методами, такими как GetAThings(), GetAThing(id), GetBThings(), GetBThing(id) и т. д.? (Но тогда в этот LookupManager потребуется несколько встроенных репозиториев или репозиторий нового типа.)

Мои размышления в сторону, мой вопрос, чтобы повторить: каков хороший способ рефакторинга этого кода, чтобы уменьшить сумасшедшее количество внедряемых зависимостей?


person Facio Ratio    schedule 20.08.2012    source источник
comment
возможный дубликат Как бороться с чрезмерным внедрением конструктора в .NET   -  person Steven    schedule 21.08.2012


Ответы (3)


Использование командной архитектуры — хорошая идея, так как это перемещает все бизнес-логику вне контроллера и позволяет добавлять сквозные задачи без изменения код. Однако это не решит вашу проблему избыточного внедрения конструктора. Стандартным решением является перемещение связанных зависимостей в агрегированный сервис. Однако я согласен с Марком в том, что вам следует обратить внимание на единицу образец работы.

person Steven    schedule 21.08.2012
comment
Спасибо за ссылки. Мне нравится этот подход. Я проведу рефакторинг в совокупную службу для своих непосредственных нужд, а затем более подробно рассмотрю архитектуру команд и единицу работы. - person Facio Ratio; 21.08.2012
comment
@Стивен, можете ли вы привести пример того, как это может работать с SimpleInjector. Я следил за вашим примером на cuttingedge.it/blogs/ steven/pivot/entry.php?id=95, и это был полезный подход. Как я могу применить фасадную или агрегатную службу. - person David Clarke; 27.01.2014
comment
@DavidClarke: я не знаю, что показать. Взгляните на шаблон Unit of Work; обычно это класс, который будет обертывать репозитории. Чтобы предотвратить чрезмерное внедрение конструктора в единицу работы, вы можете внедрить фабрику, которая знает, как разрешать репозитории. - person Steven; 27.01.2014
comment
@Стивен, так что, используя командную архитектуру, как избежать чрезмерного внедрения конструктора? Кажется, что для каждого действия нужен хотя бы один обработчик, а большинство моих контроллеров имеют более 5 действий. Описанная здесь агрегированная служба не кажутся применимыми, поскольку, конечно, вы не должны [скрывать] совокупное поведение за новой абстракцией для команды, которая уже несет единую ответственность? Вы хотели вместо этого предложить объекты параметров? (другая концепция, упомянутая в той же ссылке). - person ajbeaven; 02.08.2016

Рассматривали ли вы возможность использования шаблона проектирования единиц работы? Существует отличный пост MSDN о том, что такое единица работы. Выдержка из той статьи:

В некотором смысле, вы можете думать о единице работы как о месте для дампа всего кода обработки транзакций. Обязанности Рабочей группы заключаются в следующем:

  • Управляйте транзакциями.
  • Порядок вставки, удаления и обновления базы данных.
  • Предотвращение дублирования обновлений. При однократном использовании объекта "Единица работы" разные части кода могут помечать один и тот же объект "Счет-фактура"
    как измененный, но класс "Единица работы" выдает в базу данных только одну
    команду UPDATE.

Ценность использования паттерна Unit of Work состоит в том, чтобы освободить остальную часть вашего кода от этих проблем, чтобы в противном случае вы могли сосредоточиться на бизнес-логике.

Об этом есть несколько сообщений в блогах, но лучший, который я нашел, посвящен тому, как это реализовать, здесь. Есть и другие, на которые ссылались с этого сайта здесь и здесь.

Наконец, и, возможно, независимо от трех вышеперечисленных подходов, следует ли вместо этого иметь один, скажем, LookupManager с явными методами, такими как GetAThings(), GetAThing(id), GetBThings(), GetBThing(id) и т. д.? (Но тогда в этот LookupManager потребуется несколько встроенных репозиториев или репозиторий нового типа.)

Единица работы сможет справиться со всем этим, особенно если вы сможете реализовать общий репозиторий для большинства ваших потребностей в обработке базы данных. В вашем теге упоминается, что вы используете Entity Framework 4.3, верно?

Надеюсь это поможет!

person Mark Oreta    schedule 21.08.2012
comment
Вот (пока) еще один вариант единицы схемы работы. - person Steven; 21.08.2012
comment
Должен ли я создавать только один класс UnitOfWork, который используют все мои контроллеры? У меня уже есть GenericRepository‹T›. Этот класс будет ссылаться на каждый репозиторий? - person Facio Ratio; 21.08.2012
comment
Да, это именно то, что это сделает. Этот единый модуль также будет управлять параллелизмом элементов, когда они извлекаются из всех разных репозиториев, а также гарантирует, что у вас есть только 1 общий контекст между ними. - person Mark Oreta; 21.08.2012
comment
Затем я бы получил один класс UnitOfWork с чрезмерным внедрением конструктора. Похоже, мне нужны совокупные услуги, несмотря ни на что. - person Facio Ratio; 22.08.2012

Я думаю, что ваша главная проблема - слишком много уровней абстракции. Вы используете Entity Framework, поэтому у вас уже есть слой абстракции вокруг ваших данных, добавление еще двух слоев (по одному на сущность) через репозиторий и интерфейс диспетчера привело к большому количеству интерфейсов, от которых зависит ваш контроллер. Это не добавляет большой ценности, кроме того, YAGNI.

Я бы провел рефакторинг, избавившись от слоев репозитория и менеджера, и использовал бы «окружающий контекст».

Затем посмотрите, какие запросы ваш контроллер задает слоям менеджера. Там, где это очень просто, я не вижу проблем с запросом вашего «окружающего контекста» непосредственно в вашем контроллере — это то, что я бы сделал. Там, где они более сложны, реорганизуйте их в новый интерфейс, логически сгруппировав элементы (не обязательно по одному на объект) и используйте для этого свой IOC.

person StanK    schedule 21.08.2012