Внедрение зависимостей - структура слоя Visual Studio для ASP.NET
Я читал о DI и из того, что я могу понять, структура вашего VS решения должна быть следующей:
Ссылки на веб-проекты (UI):
- Доступ к данным (* царапает голову)
- Бизнес Логика
- DTO
Отзывы о проекте Business Logic:
- DTO
Ссылки на проект доступа к данным:
- Веб (* снова чешет голову)
- Бизнес Логика
- DTO
Кроме того, интерфейсы для конкретных реализаций должны быть сохранены в проекте Business Logic, который DA будет реализовывать в конкретном классе.
"Классическая" трехслойная структура:
Веб-ссылки
- Бизнес Логика
Бизнес Логика ссылки:
- DA
(С DTO, ссылающимися на все слои).
Что я пытаюсь понять со структурой DI, так это то, что я ценю то, что она помогает отделить каждый модуль для тестирования, а не вызывать конкретные классы внутри классов, но с тем, как ссылки устанавливаются в проекте, возникает ощущение, что есть плотная связь со слоями? Пользовательский интерфейс имеет жесткую ссылку как на DA, так и на BL (и, следовательно, должен создавать экземпляры как класса BL, так и класса DA, который реализует интерфейс, который BL принимает как часть внедрения конструктора).
Почему-то кажется, что пользовательский интерфейс теперь имеет ссылки на BL и DA. Если бы я хотел сказать реализовать IMessage с помощью метода SendMessage() (замена с SMTP-сервера, чтобы сказать поставщика SMS), мне все равно пришлось бы вызывать класс DA в пользовательском интерфейсе и передавать его в BL. Чувствует себя странно?!
Похоже, что пользовательский интерфейс решает, какую реализацию данных он хочет, вызывая экземпляр класса BL логики, который принимает конкретную реализацию класса доступа к данным на веб-уровне?
Я просто пытаюсь полностью очистить свою голову от классической структуры n-слоя в VS и быть открытым для того, насколько хорошо это передать в пользовательском интерфейсе (пользовательский интерфейс должен беспокоиться только об интерфейсе, верно?). Я думаю, что мне просто нужно, чтобы лампочка погасла по простому объяснению. Если вы можете помочь, это будет очень цениться!
PS - Сейчас я работаю над инъекцией зависимостей Марка Симанна в.NET, так что моя голова немного жареная!
2 ответа
Все зависит от того, где вы разместите свои заводы и какую ответственность вы даете каждому из них. Например, если у вас есть следующие заводы
' In the data access layer project
Public Class DataAccessFactory()
Implements IDataAccessFactory
Public Sub New()
' ...
End Sub
' ...
End Class
' In the business layer project
Public Class BusinessFactory()
Public Sub New(daFactory As IDataAccessFactory)
' ...
End Sub
' ...
End Class
Теперь, когда вы создаете бизнес-фабрику в пользовательском интерфейсе, вы сначала должны создать фабрику доступа к данным на уровне пользовательского интерфейса, чтобы вы могли внедрить ее в бизнес-фабрику. Таким образом, в этой ситуации уровень пользовательского интерфейса должен ссылаться как на бизнес, так и на уровень доступа к данным. Тем не менее, вы также можете дать больше ответственности бизнес-фабрике, например:
Public Class BusinessFactory()
Public Sub New()
_daFactory = New DataAccessFactory()
End Sub
Private _daFactory As IDataAccessFactory
' ...
End Class
Теперь уровень пользовательского интерфейса не может решить, какой уровень доступа к данным он будет использовать, но ему также больше не нужна ссылка на проект уровня доступа к данным. Во втором примере только проект бизнес-уровня требует ссылки на проект уровня доступа к данным.
Это немного менее гибко, так как вам нужно реализовать отдельную бизнес-фабрику для каждой фабрики доступа к данным, которую вы хотите поддерживать, но вы можете сделать ее более гибкой, внедрив ее следующим образом:
Public Class BusinessFactory()
Implements IBusinessFactory
Public Sub New()
_daFactory = NewDataAccessFactory()
End Sub
Private _daFactory As IDataAccessFactory
Protected Overridable Function NewDataAccessFactory() As IDataAccessFactory
Return New DataAccessFactory()
End Function
' ...
End Class
Обратите внимание, что NewDataAccessFactory
метод переопределим. Так что теперь, когда вам нужно использовать другую фабрику доступа к данным, вы можете переопределить этот же метод, не дублируя остальную фабричную логику. Например:
Public Class XmlBusinessFactory()
Inherits BusinessFactory
Public Sub New()
MyBase.New()
End Sub
Protected Overridable Function NewDataAccessFactory() As IDataAccessFactory
Return New XmlDataAccessFactory()
End Function
End Class
Конечно, все логические (не фабричные) классы все еще полностью инъецируемы. Только фабричные классы немного менее гибки. Но, с другой стороны, слой UI не только больше не должен ссылаться на уровень доступа к данным, но также упрощает создание экземпляров объектов фабрики. Все, что вам нужно сделать, это создать нужную вам фабрику, а не все ее фабрики зависимостей.
Если вам когда-нибудь нужно было сделать зависимости фабрики инъекционными, например, для модульного тестирования вашей фабрики, вы все равно могли бы сделать это путем создания производного инъецируемого класса, например так:
Public Class InjectableBusinessFactory()
Inherits BusinessFactory
Public Sub New(daFactory As IDataAccessFactory)
MyBase.New()
_daFactory = daFactory
End Sub
Private _daFactory As IDataAccessFactory
Protected Overridable Function NewDataAccessFactory() As IDataAccessFactory
Return _daFactory
End Function
End Class
Таким образом, хотя для этого требуется дополнительный шаг, это означает, что следование этой схеме не означает, что вы рисуете себя в углу. В конце концов, вам нужно взвесить все за и против и выбрать то, что работает лучше всего в вашей ситуации. Я рекомендую попробовать реализовать ваши фабрики в обоих направлениях, чтобы вы могли почувствовать оба метода.
Вы можете отделить свои интерфейсы от его реализации, поместив их в другую сборку и поместив все ваши конфигурации DI в отдельную сборку. Если вы поместите все конфигурации DI в другую сборку, тогда ваш уровень пользовательского интерфейса больше не будет ссылаться на все другие уровни. Затем вы также можете легко переключить сборку конфигурации, чтобы быстро изменить конфигурацию DI, не затрагивая уровень пользовательского интерфейса.