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

от автора

Всем привет, я Максим Кравец из Holyweb, и мы продолжаем разговор о паттернах (первую статью о Singleton можно почитать вот тут). Героя нашего сегодняшнего сюжета порой называют «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 комплектов альпинистского снаряжения под кроватью     ангельский характер      Принц Эдвард знает, что у Золушки      свой магазин платьев и обуви "Все для бала"     ангельский характер      Принц Артур знает, что у Золушки      диплом кондитера высшей категории     ангельский характер      Принц Али знает, что у Золушки      любовь к экстремальному отдыху     5 комплектов альпинистского снаряжения под кроватью     свой магазин платьев и обуви "Все для бала"     диплом кондитера высшей категории     ангельский характер            Бюджет свадьбы 101500

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

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

Если есть интерес, пишите в комментариях — или ответим сразу, или развернем тему еще в одной статье. А если хотите познакомиться с нашей командой ближе, я всегда на связи в Телеграме @maximkravec. 

ссылка на оригинал статьи https://habr.com/ru/post/554930/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *