# Decorator паттерн в Javascript

poster
В этом видео мы с вами разберем такой паттерн, как Decorator. Как и миксин, он является альтернативой наследования от класса к классу.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

В этом видео мы с вами разберем такой паттерн, как Decorator. Как и миксин, он является альтернативой наследования от класса к классу.

С помощью декоратора, мы можем динамически добавлять обьектам новые свойства и методы. То есть мы как бы заворачиваем наш обьект в декоратор, как в superclass.

Очень частый пример, когда нам выгодно использовать декораторы, это если нам понадобится для нашей системы огромного количество подклассов.

Например у нас есть базовый класс Coffee. И мы хотим создать дочерний класс Espresso, Latte, Cappuchino. И потом если мы хотим сделать каждый из них с сахаром нам нужно создавать класс EspressoWithSugar, который наследуется от Espresso, LatterWithSugar и так далее. У нас получается огромное количество дочерних классов, которых может быть огромное множество.

Эту проблему для нас может решить декоратор.

Давайте попробуем на реальном примере.

У нас есть класс Coffee, который создает нам кофе. И у него будет метод cost, который возвращает нам цену

class Coffee {
  cost () {
    return 5
  }
}

const coffee = new Coffee()
console.log(coffee.cost())

В браузере мы видим, что вывелась цена 5.

Теперь давай добавим декоратор sugar, который будет добавлять в кофе сахар, этим самым изменяя его цену.

Наш декоратор должен просто вызыватся как функция, в которую мы передаем наш обьект.

const coffee = new Coffee()
sugar(coffee)
console.log(coffee)

После вызова декоратора обьект coffee будет мутирован. Теперь давайте опишем этот декоратор.

const sugar = coffee => {
  const cost = coffee.cost()
  coffee.cost = () => cost + 1
}
const coffee = new Coffee()
sugar(coffee)
console.log(coffee.cost())

Внутри функции мы получили стоимость кофе и переопределили функцию cost, которая теперь будет возвращать новую стоимость кофе, уже включая сахар.

То есть наш декоратор мутировал обьект, который мы в него передали. Но мы по прежнему можем создавать базовые экземпляры кофе, так как мы не меняли класс.

Давайте добавим его парочку декораторов. Например по размеру кофе будет изменятся цена.

const sugar = coffee => {
  const cost = coffee.cost()
  coffee.cost = () => cost + 1
}

const small = coffee => {
  const cost = coffee.cost()
  coffee.cost = () => cost - 1
}

const large = coffee => {
  const cost = coffee.cost()
  coffee.cost = () => cost + 1
}

const coffee = new Coffee()
sugar(coffee)
large(coffee)
console.log(coffee.cost())

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

Добавим декоратор withMilk и декоратор largeWithMilk

const withMilk = coffee => {
  const cost = coffee.cost()
  coffee.cost = () => cost + 2
}

const largeWithMilk = coffee => {
  large(coffee)
  withMilk(coffee)
  const cost = coffee.cost()

  coffee.cost = () => cost
}

В декораторе largeWithMilk мы сначала вызвали два других декоратора, тем самым изменяя функцию цены и потом вернули новую цену.

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

Если у вас возникли какие-то вопросы или комментарии, пишите их прямо под этим видео.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
vadimnikee
10 месяцев назад
Это невообразимо охренительно, спасибо за четкие курсы, сейчас как раз на собеседовании будут по патернам шпилить, вот к счастью наткнулся за день и есть время подготовится, ты топ!!
monsterlessons
10 месяцев назад
Спасибо)
spekt
12 месяцев назад
Доступно и понятно! топ!
monsterlessons
12 месяцев назад
На здоровье)
pretender
1год назад назад
Здравствуйте! Не совсем понял зачем так делали? const largeWithMilk = coffee => { large(coffee) withMilk(coffee) const cost = coffee.cost() // Эта строка coffee.cost = () => cost // И эта строка } Почему не делали так: const llargeWithMilk = coffee => { large(coffee); withMilk(coffee); }; Каждый же декоратор последовательно меняет coffee.cost. И на выходе получается тот же результат. Спасибо! Очень понятно и полезно.
monsterlessons
1год назад назад
Добрый день. Если вы посмотрите, что у вас будет за функция в coffee.cost после вызова largeWithMilk, то в вашем случае это () => cost + 2 То есть, у вас cost хранится не чистый возврат значение cost из замыкания, а сложение. Переопределив функцию, мы вернем чистый результат. Но вы правы, в обоих случаях мы получим тот же результат.
fromanywhere
2 лет назад
Всё же декоратор не предполагает мутации. Предполагается, что инстанс декоратора оборачивает («декорирует») инстанс оригинального класса, дополняя поведение, но не меняя оригинал
monsterlessons
2 лет назад
Ну так мы нигде и не мутировали исходный класс.
fromanywhere
2 лет назад
В примере мутирован экземпляр coffee, о чем явно говорится в тексте. Метод cost безвозвратно переопределяется, в то время как зона ответственности декоратора — лишь хранить ссылку на оригинал, а не менять его. Предлагается сравнить с https://jsfiddle.net/4oLn3zbz/
monsterlessons
2 лет назад
Я понял, что вы имеете ввиду. Вполне можно писать и так. Но в паттерне декоратор я ни разу не встречал, что мутировать инстанс запрещено и много примеров в интернете, которые я видел тоже мутировали инстанс. Например: https://addyosmani.com/blog/decorator-pattern
Артур Владимирович
2 лет назад
Согласен, с телефона читать одно удовольствие!
monsterlessons
2 лет назад
Спасибо, очень приятно.
Тема Маклаков
2 лет назад
Какой же у Вас красивый сайт. "Сделано с любовью". Вы большой молодец!!!
monsterlessons
2 лет назад
Спасибо большое. Стараюсь.
Sergey Illarionov
2 лет назад
Супер и долгожданно! Спасибо.
monsterlessons
2 лет назад
Был в отпуске, поэтому не было новых видео. Сейчас в обычном режиме.