Как предотвратить неправильное использование конструктора в классе С#
Я пытаюсь реализовать слабосвязанное приложение в приложении asp.net MVC5. У меня есть контроллер:
public class HeaderController : Controller
{
private IMenuService _menuService;
public HeaderController(IMenuService menuService)
{
this._menuService = menuService;
}
//
// GET: /Header/
public ActionResult Index()
{
return View();
}
public ActionResult GetMenu()
{
MenuItem menu = this._menuService.GetMenu();
return View("Menu", menu);
}
}
И сервис, используемый в этом контроллере:
public class MenuService : IMenuService
{
private IMenuRespository _menuRepository;
public MenuService(IMenuRespository menuRepository)
{
this._menuRepository = menuRepository;
}
public MenuItem GetMenu()
{
return this._menuRepository.GetMenu();
}
}
И репозиторий, который используется в классе службы:
public class MenuRepository : IMenuRespository
{
public MenuItem GetMenu()
{
//return the menu items
}
}
Интерфейсы, используемые для службы и репозитория, таковы:
public interface IMenuService
{
MenuItem GetMenu();
}
public interface IMenuRespository
{
MenuItem GetMenu();
}
Конструктор для HeaderController
принимает в MenuService
с помощью Injection конструктора, и у меня есть ninject в качестве контейнера DI, обрабатывающего это.
Все работает отлично - кроме того, в моем контроллере я все еще могу сделать это:
MenuItem menu = new MenuService(new MenuRepository());
... который разрушает архитектуру. Как я могу предотвратить использование "нового" таким образом?
Ответы
Ответ 1
Один из способов сделать это - переместить ваши интерфейсы и реализации в отдельные проекты/сборки Visual Studio и только ссылаться на проект реализации в проекте, который на самом деле нуждается в нем - все остальное может ссылаться на проект интерфейса для вашего IMenuService
- в этот момент код может потреблять интерфейс, но фактически не обновлять какие-либо реализации.
Затем вы можете ссылаться на проект реализации, где бы вы ни находились в ваших зависимостях.
Решение WebApp:
WebApp Proj (контроллеры и т.д.) → Service Interface Proj
Сервис Impl Project → Service Interface Proj
Несмотря на это, это хороший подход, это не безупречное доказательство всеми средствами - другим компонентом является просвещение и обзор кода, чтобы придумать лучшие практики, которые работают для вашей команды, такие как тестируемость и инъекция зависимости.
Ответ 2
Я предполагаю, что часть проблем с ручным созданием объекта может возникнуть при работе с большой командой, в результате чего некоторые члены используют метод инсталляции конструктора неправильно. Если это так, я нашел многого, просветив их в ракурсе, разрешенном большинством проблем. Иногда вы находите, что кто-то делает это неправильно, но не часто. Другой альтернативой может быть добавление атрибута [EditorBrowsable(EditorBrowsableState.Never)]
в конструктор контроллера. Конструктор исчезнет из intellisense; ну, похоже, он исчезнет. Однако он все еще может быть использован.
Вы можете разбить реализации в другую DLL, а не напрямую ссылаться (неявно ссылаясь) на проект MVC, и, следовательно, поскольку нет прямой ссылки, вы не можете использовать эти типы напрямую. С интерфейсами в одном проекте, который ссылается каждый проект, а проект с реализацией косвенно ссылается, таким образом, будут включены только интерфейсы. Я бы рекомендовал включить прямую ссылку в проект unit test, если вы выполняете модульные тесты, чтобы повысить охват тестирования.
Ответ 3
Пара возможных вариантов (которые я никогда не пробовал, но мог бы иметь некоторые ноги):
-
возможно, вы можете написать правило FXCop, какие ошибки, если конструктор используется в коде.
-
вы можете пометить конструктор как устаревший и не скомпилировать сервер сборки, если вы используете устаревшие методы в коде.
Если контейнер DI использует его через отражение, все должно быть в порядке (хотя в случае FXCop вы, вероятно, не могли бы бросить, если бы это было в методе в пространстве имен NInject)
Ответ 4
В качестве общего принципа проектирования интерфейсы (контракты) должны быть в одной сборке, а реализация должна выполняться в другой сборке. Сборка Контрактов должна быть ссылкой в проекте MVC, и реализованная сборка должна быть скопирована в папку "bin". Вместо использования Загрузка динамического модуля" для загрузки типов. Таким образом, вы избежите вышеупомянутой проблемы, и это более широкое решение. Поскольку вы можете заменить реализацию без создания пользовательских интерфейсов и контактных сборок.