Лучшая практика определения метода класса javascript для расчета цены на продукты с начинками - PullRequest
1 голос
/ 14 октября 2019

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

interface HasPrice {
    getPrice(): number;
}

class Ramen implements HasPrice {
    getPrice() {
        return 5;
    }
}
class Spaghetti implements HasPrice {
    getPrice() {
        return 10;
    }
}

Затем она решила, что должен быть топпинг, поэтому она использовала шаблон декоратора.

class RamenWithPork extends Ramen {
    getPrice() {
         super.getPrice() + 3;
    }
}

Это работало до тех пор, пока я не решил расширить меню топпинга, и оно стало слишком громоздким, чтобы иметь дело с комбинаторным количеством классов. Как мне это исправить?

1 Ответ

0 голосов
/ 14 октября 2019

Шаблон декоратора

ОК, так что это работа шаблона проектирования декоратора - вы можете обернуть ваши объекты в другой, который украшает их дополнительным поведением. Это решает проблему запутанных иерархий зависимостей. Вы уже испытали это, но только для иллюстрации - скажем, у вас есть

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...