Один из вопросов на нашем техническом собеседовании - это вопрос о сильной и слабой связанности компонентов, и о том, какими способами ее можно достичь.
Напомню, что связанностью мы называем степень зависимости модулей системы между собой. При слабой связанности модули мало зависят от реализации других модулей, т.к. взаимодействуют исключительно через специально оговоренный, достаточно устойчивый интерфейс, который декларирует зону ответственности каждого модуля. Слабая связанность является признаком хорошего дизайна и указывает на хорошо структурированную систему.
Одним из способов снижения зависимости модулей друг от друга является внедрение зависимостей, то есть внедрение (injection) зависимого объекта в зависящий извне тем или иным способом. Таким образом, объект, которому нужен какой-либо внешний ресурс, только декларирует эту потребность, а непосредственно созданием этого ресурса занимается какой-то внешний, служебный объект.
Обычно таким объектом может быть сервис-локатор или IoC-контейнер. Мы чаще используем именно второй вариант, поскольку нам он кажется более гибким в плане конфигурирования.
Большинство IoC-контейнеров позволяет осуществлять инъекцию в свойство или в конструктор. В первом случае мы объявляем в классе специальное свойство, указывающее на используемый интерфейс внешнего объекта, и отмечаем его специальным атрибутом.
Во втором случае мы определяем специальный конструктор зависящего объекта, в котором в качестве одного из параметров указываем интерфейс необходимого ресурса.
С моей точки зрения, использование инъекции в конструктор является более удачным вариантом, чем внедрение зависимости через свойство. Во-первых, именно конструктор является местом порождения и инициализации объекта, где определяются его свойства по умолчанию. Во-вторых, мы делаем наш код более удобным и читаемым, так-так даем понять программисту, который в дальнейшем будет использовать наш код, что нам для нормального функционирования необходимы определенные внешние сервисы. В-третьих, облегчаем unit-тестирование, т.к. конструктор явно требует передачи всех зависимостей, в то время как свойство может быть не проинициализировано.
Разумеется, у этого способа есть и определенные минусы. Если наш класс зависит от большого числа внешних ресурсов, мы можем получить громоздкий конструктор, который может свести на нет все плюсы от использования такого способа DI. Но такое большое число внешних зависимостей может быть следствием нарушением принципа SOLID и сигналом к проведению рефакторинга – возможно, на наш класс возложено слишком много обязанностей и их можно распределить между несколькими другими классами.
Напомню, что связанностью мы называем степень зависимости модулей системы между собой. При слабой связанности модули мало зависят от реализации других модулей, т.к. взаимодействуют исключительно через специально оговоренный, достаточно устойчивый интерфейс, который декларирует зону ответственности каждого модуля. Слабая связанность является признаком хорошего дизайна и указывает на хорошо структурированную систему.
Одним из способов снижения зависимости модулей друг от друга является внедрение зависимостей, то есть внедрение (injection) зависимого объекта в зависящий извне тем или иным способом. Таким образом, объект, которому нужен какой-либо внешний ресурс, только декларирует эту потребность, а непосредственно созданием этого ресурса занимается какой-то внешний, служебный объект.
Обычно таким объектом может быть сервис-локатор или IoC-контейнер. Мы чаще используем именно второй вариант, поскольку нам он кажется более гибким в плане конфигурирования.
Большинство IoC-контейнеров позволяет осуществлять инъекцию в свойство или в конструктор. В первом случае мы объявляем в классе специальное свойство, указывающее на используемый интерфейс внешнего объекта, и отмечаем его специальным атрибутом.
public class AccountController : Controller { [Dependency] public IMembershipService MembershipService { get; set; } }IoC-контейнер определяет свойство, отмеченное атрибутом, затем определяет класс, который реализует заданный интерфейс, создает объект этого класса и присваивает его свойству.
Во втором случае мы определяем специальный конструктор зависящего объекта, в котором в качестве одного из параметров указываем интерфейс необходимого ресурса.
public class AccountController : Controller { private IMembershipService _membershipService; public AccountController(IMembershipService MembershipService) { _membershipService = MembershipService; } }Принцип работы IoC-контейнера в данном случае аналогичен. Контейнер просматривает все конструкторы зависящего класса, пытаясь разрешить все указанные зависимости, и выбирает наиболее специализированный из них, после чего создает объекты и передает их в конструктор.
С моей точки зрения, использование инъекции в конструктор является более удачным вариантом, чем внедрение зависимости через свойство. Во-первых, именно конструктор является местом порождения и инициализации объекта, где определяются его свойства по умолчанию. Во-вторых, мы делаем наш код более удобным и читаемым, так-так даем понять программисту, который в дальнейшем будет использовать наш код, что нам для нормального функционирования необходимы определенные внешние сервисы. В-третьих, облегчаем unit-тестирование, т.к. конструктор явно требует передачи всех зависимостей, в то время как свойство может быть не проинициализировано.
Разумеется, у этого способа есть и определенные минусы. Если наш класс зависит от большого числа внешних ресурсов, мы можем получить громоздкий конструктор, который может свести на нет все плюсы от использования такого способа DI. Но такое большое число внешних зависимостей может быть следствием нарушением принципа SOLID и сигналом к проведению рефакторинга – возможно, на наш класс возложено слишком много обязанностей и их можно распределить между несколькими другими классами.
Комментариев нет:
Отправить комментарий