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


AliExpress RU&CIS

Всем привет, я Максим Кравец из 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. 




Комментарии (2):

  1. NotToxic
    /#22980888

    Супер!
    Стиль написания статьи прост и не навязчив. Главное все доходчиво.