Паттерн Декоратор: как выдать Золушку за принца и не сойти с ума

Всем привет, я Максим Кравец из Holyweb, и мы продолжаем разговор о паттернах. Героя нашего сегодняшнего сюжета порой называют «wrapper» или «обертка», поскольку он оборачивает исходный код, но мне больше нравится название «декоратор» — оно точнее отражает не механику, а суть происходящего. Приступим.

А кто у нас муж? Волшебник? Предупреждать же надо!

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

Мы, конечно, не волшебники. Мы программисты. Но творить чудеса для нашего кода обязаны! Так что создаем класс «Золушка», наделяем ее ангельским характером и пытаемся выдать ее замуж, по возможности — удачно.

class Cinderella { aboutMe() { return `ангельский характер`; }; }

Пришла пора посмотреть на потенциальных супругов:

  1. Принц Филипп. Обожает скачки, охоту, экстремальный отдых.
  2. Принц Эдвард. Страстный поклонник танцев и бальных нарядов.
  3. Принц Артур. Любитель сладостей и выпечки.

Давайте представим нашу Золушку принцу Филлипу:

class Cinderella { aboutMe() { return `ангельский характер`; }; } const whatPrinceFilippKnows = new Cinderella() console.log('У Золушки', whatPrinceFilippKnows.aboutMe())

Результат выполнения нашего кода:

У Золушки ангельский характер

Хм… маловато будет. Принц Филипп экстремал, с ним и встретиться-то можно только во время прыжка с тарзанки. На всякий случай добавим:

class Cinderella { aboutMe() { return `ангельский характер`; }; } class ExtremeCinderella extends Cinderella { aboutMe() { return ` любовь к экстремальному отдыху 5 комплектов альпинистского снаряжения под кроватью ангельский характер ` }; } const whatPrinceFilippKnows = new ExtremeCinderella() console.log('У Золушки', whatPrinceFilippKnows.aboutMe())

Пожалуй, такой результат нашего принца удовлетворит:

У Золушки

любовь к экстремальному отдыху

5 комплектов альпинистского снаряжения под кроватью

ангельский характер

А вот принцу Эдварду экстрим — до лампочки. Он обожает танцы. Сделаем и ему Золушку его мечты, заодно немного поправим вывод в консоль, чтобы понимать, какому принцу какое счастье достанется:

class Cinderella { aboutMe() { return `ангельский характер` }; } class ExtremeCinderella extends Cinderella { aboutMe() { return ` любовь к экстремальному отдыху 5 комплектов альпинистского снаряжения под кроватью ангельский характер ` }; } class DanceCinderella extends Cinderella { aboutMe() { return ` свой магазин платьев и обуви "Все для бала" ангельский характер ` }; } const whatPrinceFilippKnows = new ExtremeCinderella() const whatPrinceEdvardKnows = new DanceCinderella() console.log('Принц Филипп знает, что у Золушки', whatPrinceFilippKnows.aboutMe()) console.log('Принц Эдвард знает, что у Золушки', whatPrinceEdvardKnows.aboutMe())

Результат выглядит вроде бы неплохо:

Принц Филипп знает, что у Золушки

любовь к экстремальному отдыху

5 комплектов альпинистского снаряжения под кроватью

ангельский характер

Принц Эдвард знает, что у Золушки

свой магазин платьев и обуви «Все для бала»

ангельский характер

Хьюстон, у нас проблема! Даже две

Первая — идеологическая. Вернемся ненадолго к опыту тети-феи, которая собирала свою крестницу на бал. Золушка — какой была изначально, такой и оставалась. В нее саму никаких изменений не вносилось! Изменялся только антураж, декорирование. Добавлялась одежда, карета, кучер, туфельки. Но Золушка оставалась Золушкой. Мы же — плодим новые классы с помощью наследования.

Вторая проблема — принцев на свете многовато. И запросы у них порой... самые причудливые. Устанешь для каждого создавать отдельный класс. Хочется как в сказке: Золушка отдельно, платье отдельно, тыква, пардон, карета — тем более отдельно. Сложить все в коробку да и выдать ее принцу, пусть сам собирает тот комплект, что его устроит! Золушка и туфельки, Золушка и образование, Золушка и месть гномов…

Впрочем, это уже про другое, нас же интересует, как реализовать задуманное. Для начала, давайте вынесем все «дополнительные опции» в отдельные функции. Вторым шагом (волшебники мы или погулять вышли?) прикажем этим функциям обернуть — задекорировать — свойства нашей исходной Золушки, чтобы при обращении к классу Cinderella возвращался не только ее ангельский характер, но и обертка.

Согласно Википедии, Декоратор — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.

На первый взгляд — вроде бы то, что доктор прописал! Осталось понять, как это реализовать. Наследование не подошло, может быть получится с агрегацией (в более строгой форме — композицией)?

Давайте еще раз внимательно взглянем на базовый класс Золушки и передадим в нашу функцию-обертку ссылку на базовый класс, а также реализуем точно такой же интерфейс. В итоге мы не будем наследовать поведение, мы добавим в обертке ее функционал, а базовую работу попросим выполнить переданную ссылку на класс.

class Cinderella { aboutMe() { return `ангельский характер` }; } function extremeSet(cinderella) { this.aboutMe = function () { return ` любовь к экстремальному отдыху 5 комплектов альпинистского снаряжения под кроватью ${cinderella.aboutMe()} ` } } function danceShop(cinderella) { this.aboutMe = function () { return ` свой магазин платьев и обуви "Все для бала" ${cinderella.aboutMe()} ` } } function pastryСhef(cinderella) { this.aboutMe = function () { return ` диплом кондитера высшей категории ${cinderella.aboutMe()} ` } } const whatPrinceFilippKnows =new extremeSet(new Cinderella()) const whatPrinceEdvardKnows = new danceShop(new Cinderella()) const whatPrinceArturKnows = new pastryСhef(new Cinderella()) const whatPrinceAliKnows = new extremeSet(new danceShop(new pastryСhef(new Cinderella()))) console.log('Принц Филипп знает, что у Золушки', whatPrinceFilippKnows.aboutMe()) console.log('Принц Эдвард знает, что у Золушки', whatPrinceEdvardKnows.aboutMe()) console.log('Принц Артур знает, что у Золушки', whatPrinceArturKnows.aboutMe()) console.log('Принц Али знает, что у Золушки', whatPrinceAliKnows.aboutMe())

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

Принц Филипп знает, что у Золушки

любовь к экстремальному отдыху

5 комплектов альпинистского снаряжения под кроватью

ангельский характер

Принц Эдвард знает, что у Золушки

свой магазин платьев и обуви "Все для бала"

ангельский характер

Принц Артур знает, что у Золушки

диплом кондитера высшей категории

ангельский характер

Принц Али знает, что у Золушки

любовь к экстремальному отдыху

5 комплектов альпинистского снаряжения под кроватью

свой магазин платьев и обуви "Все для бала"

диплом кондитера высшей категории

ангельский характер

Ах эта свадьба, свадьба пела и плясала

И это — работает! Смело отправляемся в школу волшебства за дипломом. Хотя… мало познакомить, свадьба-то тоже на наших плечах! Так что давайте не мешкая вооружимся все тем же паттерном Декоратор и посчитаем, во что нам это все обойдется.

class Cinderella { aboutMe() { return `ангельский характер` }; } function extremeSet(cinderella) { this.aboutMe = function () { return ` любовь к экстремальному отдыху 5 комплектов альпинистского снаряжения под кроватью ${cinderella.aboutMe()} ` } } function danceShop(cinderella) { this.aboutMe = function () { return ` свой магазин платьев и обуви "Все для бала" ${cinderella.aboutMe()} ` } } function pastryСhef(cinderella) { this.aboutMe = function () { return ` диплом кондитера высшей категории ${cinderella.aboutMe()} ` } } // минимальная стоимость свадьбы, просто посидеть с гостями class Wedding { price() { return 1000 } } // добавить свадебный торт function weddingCake(wedding) { this.price = function () { return wedding.price() + 200 } } // пригласить оркестр function jazzBand(wedding) { this.price = function () { return wedding.price() + 500 } } // приглашенная звезда из соседнего королевства function superStar(wedding) { this.price = function () { return wedding.price() + 100500 } } const whatPrinceFilippKnows = new extremeSet(new Cinderella()) const whatPrinceEdvardKnows = new danceShop(new Cinderella()) const whatPrinceArturKnows = new pastryСhef(new Cinderella()) const whatPrinceAliKnows = new extremeSet(new danceShop(new pastryСhef(new Cinderella()))) const weddingPrice = new superStar(new Wedding()) console.log('Принц Филипп знает, что у Золушки', whatPrinceFilippKnows.aboutMe()) console.log('Принц Эдвард знает, что у Золушки', whatPrinceEdvardKnows.aboutMe()) console.log('Принц Артур знает, что у Золушки', whatPrinceArturKnows.aboutMe()) console.log('Принц Али знает, что у Золушки', whatPrinceAliKnows.aboutMe()) console.log('Бюджет свадьбы', weddingPrice.price())

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

<...>

Принц Али знает, что у Золушки

любовь к экстремальному отдыху

5 комплектов альпинистского снаряжения под кроватью

свой магазин платьев и обуви "Все для бала"

диплом кондитера высшей категории

ангельский характер

Бюджет свадьбы 101500

...и жили они долго…

Сказочные истории принято завершать фразой про долго и счастливо. Свою задачу — организовать процесс презентации нашей Золушки потенциальному принцу — мы выполнили. Не отвертится. И даже возможные расходы посчитали! Насколько удалось при этом донести смысл и механику работы паттерна Декоратор, решать вам.

А если хотите познакомиться с нашей командой ближе, напишите нам на career@holyweb.ru

Ответим в течение 60 минут в рабочее время

Глеб Корсунов,  Директор по развитию
Глеб Корсунов,
Директор по развитию
Мария Дорохина, Менеджер по развитию
Мария Дорохина,
Менеджер по развитию
Оставить заявку

Оставьте заявку
Остальное сделаем мы

Max file size 10MB.
Uploading...
fileuploaded.jpg
Upload failed. Max size for files is 10 MB.
Отправить заявку
Глеб Корсунов,
Директор по развитию
Мария Дорохина,
Менеджер по развитию
Успешно отправлено!
Отправить новую заявку
Что-то пошло не так. Свяжитесь с нами напрямую
×