Шаблон декоратора
ОК, так что это работа шаблона проектирования декоратора - вы можете обернуть ваши объекты в другой, который украшает их дополнительным поведением. Это решает проблему запутанных иерархий зависимостей. Вы уже испытали это, но только для иллюстрации - скажем, у вас есть
class Rament {}
class RamenWithPork extends Ramen {}
class RamenWithChicken extends Ramen {}
class RamenWithMushrooms extends Ramen {}
Что произойдет, если у вас есть RamenWithChickenAndMushroom
? Вы расширяете RamenWithChicken
или RamenWithMushrooms
или просто Ramen
? Что делать, если у вас есть RamenWithPorkMushroomsAndChicken
? Прежде всего, можете ли вы вспомнить название этого класса - если вы сядете днем позже, можете ли вы вспомнить, было ли это свинина, курица и грибы или свинина, грибы и курица? Во-вторых, как вы смоделируете это в иерархии классов?
Вот где появляется шаблон декоратора - вы можете создать декораторов , например:
interface OrderAddition {}
class WithChicken implements OrderAddition {}
class WithMushrooms implements OrderAddition {}
class WithPork implements OrderAddition {}
Что может тогдаиспользовать с любой едой. interface Extras
здесь для удобства. Это помогает сфокусировать иерархию зависимостей и сделать ее более практичной - HasPrice
немного слишком расплывчато - все может иметь цену, но с OrderAddition
мы точно знаем, что это за вид. Еще один способ назвать это OrderDecorator
- он немного более сухой, но более наглядный и сразу вызывает используемый шаблон проектирования.
Использование классов
Итак, вот как может выглядеть ваш кодкак и в случае с декораторами:
interface MainDish { //renaming it to be more descriptive
getPrice(): number;
}
class Ramen implements MainDish {
getPrice() {
return 5;
}
}
class Spaghetti implements MainDish {
getPrice() {
return 10;
}
}
//decorators would need to do the same as the object they decorate,
//so the decorator implements the MainDish interface
interface OrderAddition extends MainDish {}
abstract class AbstractOrderAddition implements OrderAddition {
protected base: MainDish;
constructor(base: MainDish) {
this.base = base;
}
abstract getPrice(): number;
}
class WithChicken extends AbstractOrderAddition {
getPrice() {
return this.base.getPrice() + 5
}
}
class WithMushrooms extends AbstractOrderAddition {
getPrice() {
return this.base.getPrice() + 1
}
}
class WithPork extends AbstractOrderAddition {
getPrice() {
return this.base.getPrice() + 3
}
}
Что затем позволяет сделать следующее:
let order: MainDish = new Ramen();
order = new WithPork(order);
order = new WithMushrooms(order);
Проверка на площадке TypeScript
Использование декоратораfunctions
Это, однако, весьма формальное использование декораторов. В JavaScript вы можете закоротить некоторые из них и при этом придерживаться той же идеи. Вместо классов вы можете использовать свои декораторы в качестве функций, которые изменяют экземпляр, который им дан:
interface OrderAdditionMaker{
(additionalPrice: number) : AddToOrder
}
interface AddToOrder {
(base: MainDish) : MainDish
}
const withChicken: AddToOrder = function withChicken(base: MainDish): MainDish {
//take a reference of the original
const originalGetPrice: MainDish["getPrice"] = base.getPrice.bind(base);
//overwrite the `getPrice` method
base.getPrice = function(): ReturnType<MainDish["getPrice"]> {
return originalGetPrice() + 5;
}
return base;
}
Это может быть дополнительно обобщено в более функциональный маршрут с помощью каррирования, чтобы вы могли легко получить все функции декоратора:
//interface for making decorators
interface OrderAdditionMaker{
(additionalPrice: number) : AddToOrder
}
//decorator function
interface AddToOrder {
(base: MainDish) : MainDish
}
const withExtra: OrderAdditionMaker = function (additionalPrice: number) {
return function (base: MainDish): MainDish {
const originalGetPrice: MainDish["getPrice"] = base.getPrice.bind(base);
base.getPrice = function (): ReturnType<MainDish["getPrice"]> {
return originalGetPrice() + additionalPrice;
}
return base;
}
}
const withChicken: AddToOrder = withExtra(5);
const withMushrooms: AddToOrder = withExtra(1);
const withPork: AddToOrder = withExtra(3);
Проверка на площадке TypeScript