Введение

Приветствую, товарищи разработчики!
Сегодня мы отправляемся в мир внедрения зависимостей в JavaScript без внешних библиотек.

Приготовьтесь к захватывающему изучению этой мощной техники, пока мы углубляемся в создание кофейни Джейн. Проявив немного прагматизма и немного технической хитрости, мы раскроем секреты внедрения зависимостей, что позволит вам создавать гибкие, масштабируемые и удобные в сопровождении приложения JavaScript.

Программное обеспечение, в отличие от аппаратного обеспечения, должно легко изменяться. Отсюда и название.

Раскрытие сущности внедрения зависимостей

Прежде чем мы углубимся в практическую реализацию, давайте разберемся в сути внедрения зависимостей.

Представьте себе кофейню Джейн, оживленный центр любителей кофеина. Джейн, знаток кофе, понимает, что она не может преуспеть во всех аспектах своего бизнеса в одиночку. Вместо этого ей помогает продавец кофе (Coffee Guru), который предоставляет бобы, и бариста (бариста Боб), который превращает эти бобы в ☕️. Это позволяет Джейн сосредоточиться на том, что у нее получается лучше всего — управлении своим бизнесом и подаче восхитительных чашек кофе своим постоянным клиентам — путем делегирования ответственности за покупку зерен, их обжарку и, наконец, помол их для приготовления кофе.

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

Настройка сцены

Чтобы начать наше путешествие, мы закладываем основу для нашего приложения. Мы инициализируем простой проект NodeJS с именем coffee-shop со следующей структурой каталогов. Код для него доступен на https://github.com/vjrngn/coffee-shop

Чтобы выразить кофейню Джейн в коде, мы создадим несколько классов. Класс CoffeeGuru — наш поставщик. Он обеспечивает все сырье, необходимое для приготовления кофе.

class CoffeeGuru {
  getCoffeeBeans() {
    // Fetch the finest coffee beans
  }

  getWater() {
    // Retrieve purified water
  }

  getMilk() {
    // Procure fresh milk
  }
}

Класс Barista отвечает за фактическое приготовление кофе.

class Barista {
  brewCoffee() {
     // ... makes amazing coffee
  }
}

Наконец, мы создаем класс CoffeeShop, чтобы собрать все вместе. Давайте на самом деле посмотрим, как они все сочетаются друг с другом.

class CoffeeShop {
  constructor(barista) {
    this.barista = barista;
  }

  makeCappuccino() {
    return this.barista.brewCoffee();
  }
}

Использование возможностей внедрения зависимостей

Джейн использует CoffeeGuru для получения всех необходимых материалов, поэтому давайте воспользуемся экземпляром CoffeeGuru, «внеся» его в Barista .

class Barista {
  constructor(coffeeGuru) {
    this.ingredientProvider = coffeeGuru;
  }

  brewCoffee() {
    // ...
  }
}

В коде внедрение означает создание класса с экземпляром CoffeeGuru . Это известно как внедрение конструктора. Существуют и другие формы внедрения, такие как внедрение метода и внедрение свойства, но наиболее простым и широко используемым является внедрение конструктора.

Так просто! Давайте приготовим горячий, обжигающий капучино!

Мы обновим наши Barista, чтобы они могли использовать все замечательные бобы, молоко и другие ингредиенты из CoffeeGuru.

class Barista {
  constructor(coffeeGuru) {
    this.ingredientProvider = coffeeGuru;
  }

  brewCoffee() {
    const coffeeBeans = this.ingredientProvider.getCoffeeBeans();
    const water = this.ingredientProvider.getWater();
    const milk = this.ingredientProvider.getMilk();

    // Brew the perfect cup of cappuccino using the ingredients
    // ...
  }
}

Объединяя все это

Теперь, когда у нас есть наш продавец и бариста, готовые к работе, мы можем начать продавать наш кофе клиентам.

Начнем с создания экземпляра CoffeeGuru . Затем мы создаем экземпляр Barista, вставляя в него экземпляр CoffeeGuru. Наконец, мы создаем наш CoffeeShop и заставляем его использовать (или внедрять) Barista .

Благодаря профессиональному руководству кофейного гуру и искусству введения зависимостей Джейн теперь может обслуживать своих клиентов лучшим кофеином.

Фу! Это было МНОГО работы, чтобы служить ☕️.

«Зачем проходить через все эти хлопоты? 🤔», — спросите вы. Хороший вопрос!

Чтобы понять, почему этот метод так эффективен, предположим, что Джейн решает, что ее продавец слишком дорог, и она больше не может покупать у него. В этом надуманном примере это изменение простое. Тем не менее, он демонстрирует очень распространенное явление в разработке программного обеспечения, когда все часто меняется. Например, мы решили использовать Stripe вместо RazorPay для приема платежей клиентов, использовать чистый SQL вместо ORM для выполнения запросов к базе данных и т. д.

Вы очень скоро поймете, что справиться со всем этим без DI становится несостоятельным. Программное обеспечение, в отличие от аппаратного обеспечения, должно легко изменяться. Отсюда и название. DI — это один из методов, который позволяет программному обеспечению быть… «мягким».

Чтобы иметь дело со сменой поставщика, все, что нам нужно сделать, это создать еще один класс, скажем, CoffeeCo, и использовать его вместо CoffeeGuru.

Таким образом, нам нужно было только сменить поставщика и ничего не нужно было менять в процессе приготовления кофе! Потрясающе, правда!?🔥

Давайте сделаем быстрый рефакторинг

Самые проницательные из вас заметили бы, что я назвал переменную this.ingredientProvider в классе Barista вместо this.coffeeGuru .

class Barista {
  constructor(coffeeGuru) {
    this.ingredientProvider = coffeeGuru;
  }
  
  brewCoffee() {
    const coffeeBeans = this.ingredientProvider.getCoffeeBeans();
    const water = this.ingredientProvider.getWater();
    const milk = this.ingredientProvider.getMilk();
    // Brew the perfect cup of cappuccino using the ingredients
    // ...
  }
}

Поскольку Barista на самом деле не волнует, какой именно поставщик предоставляет bean-компоненты, такое имя нашей переменной позволяет нам ссылаться на поставщика в более абстрактной форме, а не использовать this.coffeeGuru, поскольку поставщик может измениться в будущем. Теперь мы можем обновить наш класс Barista следующим образом:

class Barista {
  constructor(ingredientProvider) {
    this.ingredientProvider = ingredientProvider;
  }
  
  brewCoffee() {
    const coffeeBeans = this.ingredientProvider.getCoffeeBeans();
    const water = this.ingredientProvider.getWater();
    const milk = this.ingredientProvider.getMilk();
    // Brew the perfect cup of cappuccino using the ingredients
    // ...
  }
}

Это изменение незначительное и не влияет на функциональность класса Barista. Однако, поскольку код больше читается, чем пишется, это усиливает намерение, поскольку зависимость ingredientProvider ясно указывает, что Barista нужен поставщик для предоставления ингредиентов, но не заботится о конкретном поставщике, таком как CoffeeGuru .

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

Если вам понравилась эта статья и вы чему-то научились, не стесняйтесь… проявите любовь с помощью 👏 . Это действительно помогает мне охватить более широкую аудиторию и мотивирует писать больше полезных статей. 🙌🏻

До скорого…