[Разработка веб-сайтов, JavaScript, Программирование, Совершенный код] Погружение во внедрение зависимостей (DI), или как взломать Матрицу
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Давным-давно в далекой Галактике, когда сестры Вачовски еще были братьями, искусственный разум в лице Архитектора поработил человечество и создал Матрицу… Всем привет, это снова Максим Кравец из Holyweb, и сегодня я хочу поговорить про Dependency Injection, то есть про внедрение зависимостей, или просто DI. Зачем? Возможно, просто хочется почувствовать себя Морфеусом, произнеся сакраментальное: «Я не могу объяснить тебе, что такое DI, я могу лишь показать тебе правду».
Постановка задачи
Вот. Взгляни на этих птиц. Существует программа, чтобы ими управлять. Другие программы управляют деревьями и ветром, рассветом и закатом. Программы совершенствуются. Все они выполняют свою собственную часть работы.Пифия
Фабула, надеюсь, всем известна — есть Матрица, к ней подключены люди. Люди пытаются освободиться, им мешают Агенты. Главный вопрос — кто победит? Но это будет в конце фильма, а мы с вами пока в самом начале. Так что давайте поставим себя на место Архитектора и подумаем, как нам создать Матрицу?Что есть программы? Те самые, которые управляют птицами, деревьями, ветром.Да просто всем нам знакомые классы, у каждого из которых есть свои поля и методы, обеспечивающие реализацию возложенной на этот класс задачи.Что нам нужно обеспечить для функционирования Матрицы? Механизм внедрения, или (внимание, рояль в кустах), инжекции (Injection) функционала классов, отвечающих за всю вышеперечисленную флору, фауну и прочие природные явления, внутрь Матрицы. Подождем, пока грузчики установят в кустах очередной музыкальный инструмент, и зададимся вопросом: а что произойдет с Матрицей после того, как мы в нее инжектируем нужный нам функционал? Все правильно — у нее появятся зависимости (Dependency) от внешних по отношению к ней классов.Пазл сложился? С одной стороны — да. Dependency Injection — это всего лишь механизм внедрения в класс зависимости от другого класса. С другой — что это за механизм, для чего он нужен и когда его стоит использовать? Первым делом, посмотрим на цитату в начале текста и обратим внимание на предложение: «Программы совершенствуются». То есть — переписываются. Изменяются. Что это означает для нас? Работа нашей Матрицы не должна зависеть от конкретной реализации класса зависимости. Кажется, ерунда какая-то — зависимость на то и зависимость, чтобы от нее зависеть! А теперь следите за руками. Мы внедряем в Матрицу не конкретную реализацию зависимости, а абстрактный контракт, и реализуем механизм предоставления конкретной реализации, соответствующей этому контракту! Остались сущие пустяки — понять, как же это все реализовать.Внедрение зависимости в чистом видеОставим романтикам рассветы и закаты, птичек и цветочки. Мы, человеки, должны вырваться из под гнета ИИ вообще и Архитектора в частности. Так что будем разбираться с реализацией DI и параллельно — освобождаться из Матрицы. Первая итерация. Создадим класс matrix, непосредственно в нем создадим агента по имени Смит, определим его силу. Там же, внутри Матрицы, создадим и претендента, задав его силу, после чего посмотрим, кто победит, вызвав метод whoWin():
class Matrix {
agent = {
name: 'Smith',
damage: 10000,
};
human = {
name: 'Cypher',
damage: 100,
};
whoWin(): string {
const result = this.agent.damage > this.human.damage
? this.agent.name
: this.human.name;
return result;
}
}
const matrixV1 = new Matrix();
console.log(‘Побеждает ’, matrixV1.whoWin());
Да, Сайфер не самый приятный персонаж, да еще и хотел вернуться в Матрицу, так что на роль первого проигравшего подходит идеально.
Побеждает Smith
Архитектор, конечно, антигерой в рамках повествования, но идея заставить его вручную внести каждого подключенного к Матрице в базовый класс, а потом еще отслеживать рождаемость-смертность и поддерживать код в актуальном состоянии — сродни путевке в ад. Тем более, что физически люди находятся в реальном мире, а в Матрицу проецируется только их образ. Хотите — называйте его аватаром. Мы программисты, нам ближе ссылка или инстанс. Перепишем наш код.
class Human {
name;
damage;
constructor(name, damage) {
this.name = name;
this.damage = damage;
}
get name(): string {
return this.name;
}
get damage(): number {
return this.damage;
}
}
class Matrix {
agent = {
name: 'Smith',
damage: 10000,
};
human;
constructor(challenger) {
this.human = challenger;
}
whoWin(): string {
const result = this.agent.damage > this.human.damage
? this.agent.name
: this.human.name;
return result;
}
Мы добавили класс Human, принимающий на вход конструктора имя и силу человека, и возвращающий их в соответствующих методах. Также мы внесли изменения в наш класс Матрицы — теперь информацию о претенденте на победу он получает через конструктор. Давайте проверим, сможет ли Тринити победить Агента Смита?
const Trinity = new Human('Trinity', 500);
const matrixV1 = new Matrix(Trinity);
console.log('Побеждает ', matrixV1.whoWin());
Увы, Тринити «всего лишь человек» (с), и ее сила по определению не может быть больше, чем у агента, так что итог закономерен.
Побеждает Smith
Но стоп! Давайте посмотрим, что случилось с Матрицей? А случилось то, что класс Matrix и результаты его работы стал зависеть от класса Human! И нашему оператору, отправляющему Тринити в Матрицу, достаточно немного изменить код, чтобы обеспечить победу человечества!
class Human {
…
get damage(): number {
return this.damage * 1000;
}
}
...Пьем шампанское и расходимся по домам? Чем плох подход выше? Тем, что класс Matrix ждет от зависимости challenger, передаваемой в конструктор, наличие метода damage, поскольку именно к нему мы обращаемся в коде. Но об этом знает Архитектор, создавший Матрицу, а не наш оператор! В примере — мы можем угадать. А если не знать заранее название метода? Может быть, надо было написать не damage, а power? Или strength? Инверсия зависимостейЗнакомьтесь! Dependency inversion principle, принцип инверсии зависимостей (DIP). Название, кстати, нередко сокращают, убирая слово «принцип» , и тогда остается только Dependency inversion (DI), что вносит путаницу в мысли новичков.Принцип инверсии зависимостей имеет несколько трактовок, мы приведем лишь две:
- Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Зачем он нам понадобился? Для того, чтобы обеспечить механизм соблюдения контракта для зависимости, о необходимости которого мы говорили при постановке задачи, и отсутствие которого в нашей реализации не дало нам пока наполнить бокалы шампанским.Давайте внедрим в наш класс Matrix некий абстрактный класс AbstractHuman, а конкретную реализацию в виде класса Human — попросим имплементировать эту абстракцию:
abstract class AbstractHuman {
abstract get name(): string;
abstract get damage(): number;
}
class Human implements AbstractHuman{
name;
damage;
constructor(name, damage) {
this.name = name;
this.damage = damage;
}
get name(): string {
return this.name;
}
get damage(): number {
return this.damage;
}
}
class Matrix {
agent = {
name: 'Smith',
damage: 10000,
};
human;
constructor(challenger: AbstractHuman) {
this.human = challenger;
}
whoWin(): string {
const result = this.agent.damage > this.human.damage
? this.agent.name
: this.human.name;
return result;
}
}
const Morpheus = new Human('Morpheus', 900);
const matrixV2 = new Matrix(Morpheus);
console.log('Побеждает ', matrixV2.whoWin());
Морфеуса жалко, но все же он — не избранный.
Побеждает Smith
Вторая версия Матрицы пока что выигрывает, но что получилось на текущий момент? Класс Matrix больше не зависит от конкретной реализации класса Human — задачу номер один мы выполнили. Класс Human отныне точно знает, какие методы с какими именами в нем должны присутствовать — пока «контракт» в виде абстрактного класса AbstractHuman не будет полностью реализован (имплементирован) в конкретной реализации, мы будем получать ошибку. Задача номер два также выполнена.Порадуемся за архитектора, кстати! В новой реализации он может создать отдельный класс для мужчин, отдельный для женщин, если понадобится — сделать отдельный класс для брюнеток и отдельный для блондинок… Согласитесь, таким модульным кодом легче управлять.
В бою с Морфеусом побеждает Smith
В бою с Тринити побеждает Smith
Думаю, вы уже догадались, что должен сделать наш оператор, чтобы Нео все же победил. Напишем еще один класс для Избранного, слегка отредактировав его силу:
...
class TheOne implements AbstractHuman{
name;
damage;
constructor(name, damage) {
this.name = name;
this.damage = damage;
}
get name(): string {
return this.name;
}
get damage(): number {
return this.damage * 1000;
}
}
…
const Neo = new TheOne('Neo, 500);
const matrixV5 = new Matrix(Neo);
Свершилось!
В бою с Нео побеждает Нео
Инверсия управления Давайте посмотрим, кто управляет кодом? В нашем примере мы сами пишем и класс Matrix, и класс Human, сами создаем инстансы и задаем все параметры. Мы управляем нашим кодом. Захотели — внесли изменения и обеспечили победу Тринити. Увы, по условиям мира, придуманного Вачовски, мы можем лишь вклиниваться в работу Матрицы, добавляя свои кусочки программного кода. Матрица управляет не только фантомами внутри себя, но и тем, как с ней работать извне!Возможно, авторы трилогии увлекались программированием, потому что ситуация целиком и полностью списана с реальности и даже имеет свое название — Inversion of Control (IoC). Когда программист работает в фреймворке, он тоже пишет только часть кода, отдельные модули (классы, в которые внедряются зависимости) или сервисы (классы, которые внедряются в модули как зависимости). Причем какие именно (порой вплоть до правил именования файлов) — решает фреймворк. Кстати, уже использованный нами выше DIP (принцип инверсии зависимостей) — одно из проявлений механизма IoC.К-контейнер н-нада? Последний шаг — передача управления разрешением зависимостей. Кому и какой инстанс предоставить, использовать singleton или multiton — также решается не программистом (оператором), а фреймворком (Матрицей). Вариантов решения задачи множество, но все они сводятся к одной идее.
- на верхнем уровне приложения создается глобальный объект,
- в этом объекте регистрируется абстрактный интерфейс и класс, который его имплементирует,
- модуль запрашивает необходимый ему интерфейс (абстрактный класс),
- глобальный объект находит класс, имплементирующий данный интерфейс, при необходимости создает инстанс и передает его в модуль.
Конкретные реализации у каждого фреймворка свои: где-то используется Локатор сервисов/служб (Service Locator), где-то Контейнер DI, чаще называемый IoC Container. Но на уровне базовой функциональности отличия между подходами стираются до неразличимости.У нас есть класс, который мы планируем внедрить (сервис). Мы сообщаем фреймворку о том, что этот класс нужно отправить в контейнер. Наиболее наглядно это происходит в Angular — мы просто вешаем декоратор Injectable.
@Injectable()
export class SomeService {}
Декоратор добавит к классу набор метаданных и зарегистрирует его в IoC контейнере. Когда нам понадобится инстанс SomeService, фреймворк обратится к контейнеру, найдет уже существующий или создаст новый инстанс сервиса и вернет его нам. Крестики-нолики, а точнее — плюсы и минусыПлюсы очевидны — вместо монолитного приложения мы работаем с набором отдельных сервисов и модулей, не связанных друг с другом напрямую. Мы не завязаны на конкретную реализацию класса, более того, мы можем подменять их при необходимости просто через конфигурацию. За счет того, что большая часть «технического» кода отныне спрятана в недрах фреймворка, наш код становится компактнее, чище. Его легче рефакторить, тестировать, проводить отладку.Минусы — написание рабочего кода требует понимания логики работы фреймворка, иначе проект превращается в набор «черных ящиков» с наклейками «я реализую такой-то интерфейс». Кроме того, за любое удобство в программировании приходится платить производительностью. В конечном итоге все всё равно сводится к обычному инстанцированию с помощью new, а дополнительные «обертки», реализующие за нас эту логику, требуют и дополнительных ресурсов.Вместо заключения, или как это использовать практически?Окей, если необходимость добавления промежуточного слоя в виде «контракта» более-менее очевидна, то где на практике нам может пригодиться IoC? Кейс 1 — тестирование.
- У вас есть модуль, который отвечает за оформление покупки в интернет-магазине.
- Функционал списания средств мы вынесем в отдельный сервис и внедрим его через DI. Этот сервис будет обращаться к реальному эквайрингу банка Х.
- Нам нужно протестировать работу модуля в целом, но мы не готовы совершать реальную покупку при каждом тесте.
- Решение — напишем моковый сервис, имплементирующий тот же контракт, что и «боевой», и для теста — через IoC будем вызывать моковую реализацию.
Кейс 2 — расширение функционала.
- Модуль — прежний, оформление покупки в интернет-магазине.
- Поступает задача — добавить возможность оплаты не только в банке Х, но и в банке Y.
- Мы пишем еще один платежный сервис, реализующий взаимодействие с банком Y и имплементирующий тот же контракт, что и сервис банка X.
- Плюсы — мы можем подменять банк, мы можем давать пользователю выбор, в какой банк платить. При этом мы никак не меняем наш основной модуль.
Кейс 3 — управление на уровне инфраструктуры.
- Модуль — прежний.
- Для production — работаем с «боевым» сервисом платежей.
- Для разработки — подгружаем моковый вариант, симулирующий списание средств.
- Для тестового окружения — пишем третий сервис, который будет симулировать списание средств, а заодно вести расширенный лог состояния.
Надеюсь, этот краткий список примеров вас убедил в том, что вопроса, использовать или не использовать DI, в современной разработке не стоит. Однозначно использовать. А значит — надо понимать, как это работает. Надеюсь, мне удалось не только помочь Нео в его битве со Смитом, но и вам в понимании, как устроен и работает DI. Если есть вопросы или дополнения по теме, буду рад продолжить общение в комментариях. Напишите, о чем рассказать в следующей статье? А если хотите познакомиться с нами ближе, я всегда на связи в Телеграме @maximkravec.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Julia, Искусственный интеллект, Data Engineering] Новая система автоматически очищает массивы ненадёжных данных (перевод)
- [Высокая производительность, PostgreSQL, Программирование, .NET, SQL] Как реляционная СУБД делает JOIN?
- [.NET, C#] The '?.' Operator in foreach Will Not Protect From NullReferenceException
- [Веб-дизайн, Разработка веб-сайтов, Платежные системы, JavaScript, Дизайн мобильных приложений] Создаём королевскую форму для приёма банковских карт
- [Data Mining, Big Data, Разработка под e-commerce, Data Engineering] Летний Х5 TECH FUTURE NIGHT уже 18 июня
- [Разработка веб-сайтов, JavaScript, Клиентская оптимизация, HTML, VueJS] Проблемы рендера 7-и тысяч элементов на Vuetify
- [Разработка веб-сайтов, CSS, HTML, История IT, Научно-популярное] 25 лет CSS (перевод)
- [Python, Алгоритмы, Big Data, Машинное обучение, Искусственный интеллект] DataScience Digest — 02.06.21
- [Программирование, C++, C] Динамическая типизация C
- [Программирование, Управление проектами] Если у вас нашли SCRUM
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_programmirovanie (Программирование), #_sovershennyj_kod (Совершенный код), #_di, #_dependency, #_dependency_injection, #_javascript, #_design_patterns, #_dip, #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_programmirovanie (
Программирование
), #_sovershennyj_kod (
Совершенный код
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:21
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Давным-давно в далекой Галактике, когда сестры Вачовски еще были братьями, искусственный разум в лице Архитектора поработил человечество и создал Матрицу… Всем привет, это снова Максим Кравец из Holyweb, и сегодня я хочу поговорить про Dependency Injection, то есть про внедрение зависимостей, или просто DI. Зачем? Возможно, просто хочется почувствовать себя Морфеусом, произнеся сакраментальное: «Я не могу объяснить тебе, что такое DI, я могу лишь показать тебе правду». Постановка задачи Вот. Взгляни на этих птиц. Существует программа, чтобы ими управлять. Другие программы управляют деревьями и ветром, рассветом и закатом. Программы совершенствуются. Все они выполняют свою собственную часть работы.Пифия
class Matrix {
agent = { name: 'Smith', damage: 10000, }; human = { name: 'Cypher', damage: 100, }; whoWin(): string { const result = this.agent.damage > this.human.damage ? this.agent.name : this.human.name; return result; } } const matrixV1 = new Matrix(); console.log(‘Побеждает ’, matrixV1.whoWin()); Побеждает Smith
class Human {
name; damage; constructor(name, damage) { this.name = name; this.damage = damage; } get name(): string { return this.name; } get damage(): number { return this.damage; } } class Matrix { agent = { name: 'Smith', damage: 10000, }; human; constructor(challenger) { this.human = challenger; } whoWin(): string { const result = this.agent.damage > this.human.damage ? this.agent.name : this.human.name; return result; } const Trinity = new Human('Trinity', 500);
const matrixV1 = new Matrix(Trinity); console.log('Побеждает ', matrixV1.whoWin()); Побеждает Smith
class Human {
… get damage(): number { return this.damage * 1000; } }
abstract class AbstractHuman {
abstract get name(): string; abstract get damage(): number; } class Human implements AbstractHuman{ name; damage; constructor(name, damage) { this.name = name; this.damage = damage; } get name(): string { return this.name; } get damage(): number { return this.damage; } } class Matrix { agent = { name: 'Smith', damage: 10000, }; human; constructor(challenger: AbstractHuman) { this.human = challenger; } whoWin(): string { const result = this.agent.damage > this.human.damage ? this.agent.name : this.human.name; return result; } } const Morpheus = new Human('Morpheus', 900); const matrixV2 = new Matrix(Morpheus); console.log('Побеждает ', matrixV2.whoWin()); Побеждает Smith
В бою с Морфеусом побеждает Smith
В бою с Тринити побеждает Smith ...
class TheOne implements AbstractHuman{ name; damage; constructor(name, damage) { this.name = name; this.damage = damage; } get name(): string { return this.name; } get damage(): number { return this.damage * 1000; } } … const Neo = new TheOne('Neo, 500); const matrixV5 = new Matrix(Neo); В бою с Нео побеждает Нео
@Injectable()
export class SomeService {}
=========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ), #_javascript, #_programmirovanie ( Программирование ), #_sovershennyj_kod ( Совершенный код ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:21
Часовой пояс: UTC + 5