Всем привет, я Максим Кравец из Holyweb, и мы продолжаем разговор о паттернах (первую статью о Singleton можно почитать вот тут). Героя нашего сегодняшнего сюжета порой называют «wrapper» или «обертка», поскольку он оборачивает исходный код, но мне больше нравится название «декоратор» — оно точнее отражает не механику, а суть происходящего. Приступим.
А кто у нас муж? Волшебник? Предупреждать же надо!
Хорошо Золушке — у нее тетя не просто так крестная, а целая добрая фея. Взмах волшебной палочки — и тыква превращается в карету. Еще взмах — и рабочая одежда становится бальным платьем. Две минуты волшебства — и у любого принца шансы отвертеться устремляются к нулю.
Мы, конечно, не волшебники. Мы программисты. Но творить чудеса для нашего кода обязаны! Так что создаем класс «Золушка», наделяем ее ангельским характером и пытаемся выдать ее замуж, по возможности — удачно.
class Cinderella {
aboutMe() {
return `ангельский характер`;
};
}
Пришла пора посмотреть на потенциальных супругов:
Принц Филипп. Обожает скачки, охоту, экстремальный отдых.
Принц Эдвард. Страстный поклонник танцев и бальных нарядов.
Принц Артур. Любитель сладостей и выпечки.
Давайте представим нашу Золушку принцу Филлипу:
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.