Дизайн декоратора и шаблоны фабричного дизайна

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

Итак, я знаю, как работать с шаблоном декоратора, это просто часть пользовательского ввода, которая меня застряла. Допустим, пользователь хочет приготовить пиццу, он сначала выберет размер пиццы, а затем добавит столько начинки, сколько захочет. Затем, когда они закончат, они увидят свою общую цену за то, что они добавили, а также за то, что они добавили (например, квитанция). Это на Яве.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
1 061
5

Ответы 5

С точки зрения концепции, в шаблоне декоратора выходные данные одной обработки используются в качестве входных данных для другой обработки.

Итак, в вашем случае это должно быть так:

getToppingFoo(getToppingBar(...(getXBaseSizePizzaCost())

который решает:

FooToppingCost + (BarToppingCost + ... ( Cost of pizza with base of X size )

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

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

Лучшим примером пиццы будет урок пиццы, который может делать следующие вещи:

  • подавать пиццу
  • подавать безалкогольные напитки

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

public class Pizzeria {
    public String orderPizza() {
        System.out.println("you ordered a pizza");
    }

    public String orderSoftDrink() {
        System.out.println("you ordered a soft drink");
    }
}

Чтобы реализовать здесь шаблон декоратора, мы обертываем существующий класс Pizzeria, а затем добавляем новую функцию public String orderPizza() { System.out.println("вы заказали пиццу"); } public String orderSoftDrink() { System.out.println("вы заказали безалкогольный напиток"); } реальность:

public class NewPizzeria {
    private Pizzeria pizzeria;

    public NewPizzeria() {
        pizzeria = new Pizzeria();
    }

    public String orderPizza() {
        pizzeria.orderPizza();
    }

    public String orderSoftDrink() {
        pizzeria.orderSoftDrink();
    }

    public String orderSalad() {
        System.out.println("you ordered a salad");
    }
}

Ключевым моментом здесь является то, что класс NewPizzeria «владеет» своим собственным Pizzeria объектом. По большей части он просто повторяет ту же функциональность, которая уже есть у Pizzeria. Но он также добавляет некоторые новые собственные функции.

Шаблон проектирования декоратора полезен в тех случаях, когда уже существует класс, который в основном соответствует вашим потребностям, но вам нужно что-то еще а также, вы также не можете переписать этот класс (например, потому что он является частью какой-то библиотеки). В этом случае хорошим вариантом является обертывание этого класса и использование шаблона декоратора.

Хорошие моменты, но я отмечу, что конкретный пример ОП, по сути, взят из «Шаблоны проектирования Head First» и его обсуждения кофейни.

KevinO 28.03.2019 05:21

@KevinO Ну, похоже, я с головой прыгнул в неправильный ответ LOL: P

Tim Biegeleisen 28.03.2019 05:22

@ Джордж, вот как ты говоришь спасибо в SO: stackoverflow.com/help/someone-answers

c0der 28.03.2019 07:10

Давайте посмотрим ниже пункты, чтобы начать с

  1. Шаблон декоратора в чистом виде намерен enhance existing behavior of an object at run time without destroying the existing interface of the object .
  2. Оформление подразумевает улучшение существующего поведения объекта.
  3. Декорированный объект имеет тот же (базовый) интерфейс, что и базовый декорируемый объект.

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

Ответ: Используя шаблон декоратора, также известный как обертка.

Пример: у вас есть файл, который можно зашифровать, скажем, методов шифрования в настоящее время 5, результатом будет зашифрованный файл. Зашифрованный файл можно снова зашифровать. Кроме того, давайте предположим, что есть 5 способов заархивировать файл, который впоследствии также может увеличиться. Файл может быть зашифрован с помощью метода EA, затем может быть заархивирован с помощью метода ZA, а затем снова может быть зашифрован с помощью метода EB, аналогичные последовательности могут создавать разные файлы результатов.

Один из хороших способов, как показано ниже.

public class TextFile{
       public void create(){/*somecode*/};
       public void format(){//code for default plain text};
}

public class AEncryptedFile extends TextFile{
        private TextFile wrapped;
        public AEncryptedFile(TextFile file){
              this.wrapped = file;
        }
        public void format(){
               super.format();
               //add enhacements for encryption type A
        }
}

public class BEncryptedFile extends TextFile{
        private TextFile wrapped;
        public BEncryptedFile(TextFile file){
              this.wrapped = file;
        }
        public void format(){
               super.format();
               //add enhacements for encryption type B
        }
}

public class AZippedFile extends TextFile{
        private TextFile wrapped;
        public BEncryptedFile(TextFile file){
              this.wrapped = file;
        }
        public void format(){
               super.format();
               //add enhacements for zip type A
        }
}

public class BZippedFile extends TextFile{
        private TextFile wrapped;
        public BEncryptedFile(TextFile file){
              this.wrapped = file;
        }
        public void format(){
               super.format();
               //add enhacements for zip type B
        }
}

public void UserClass{
    public static void main(String[] args){
          TextFile file = new BZippedFile(new AEncryptedFile(new TextFile()));
          file.format();
}

В приведенном выше примере кода можно сказать

Объект TextFile был украшен (путем обертывания) объектом AEncryptedFile, который дополнительно декорирован BZippedFile, в каждом из этих украшений были сделаны дополнительные улучшения для существующего объекта.

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

Примечание : Реализация шаблона декоратора имеет структуру LinkedList.

Декоратор — это класс, который расширяет функциональность другого класса. Декоратор обычно реализует тот же интерфейс, чтобы можно было использовать декорированный объект вместо базового. Хорошим примером является компрессор и/или шифровальщик, примененный к файлу или, в более общем смысле, к реализации потока данных, как показано в ответ автором @ниц.кк.

В случае пиццы мы должны определить, какое поведение нам нужно:

public interface Pizza {
    public String getIngredients();  // comma separated
    public double getTotalPrice();
}

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

public class PizzaIngredient {
    private double getPrice() {
        return 0.0;
    }
}

База пиццы сама по себе является самой простой пиццей, поэтому она должна реализовывать интерфейс Pizza. Его атрибутом является размер (и, конечно же, цена). Мы могли бы реализовать размер как отдельный класс, но я не считаю это разумным — он недостаточно универсален, чтобы быть полезным вне вселенной пиццы, и недостаточно сложен, чтобы заслуживать собственный интерфейс.

public class PizzaBase extends PizzaIngredient implements Pizza {
    public PizzaBase(String size) {
        this.size = size;
    }

    public String getIngredients() {
        return size + " base";  // the only ingredient is this base
    }
    public double getTotalPrice() {
        return getPrice();      // the base-only pizza costs the base cost
    }
    private double getPrice() {
        if (size == "small")
            return 2.0;
        if (size == "medium")
            return 2.5;

        return 3.0;            // large and undefined
    }

    private final String size;
}

Теперь нам нужны начинки. Они будут добавлены поверх пиццы в качестве декораторов: пицца плюс начинка — это тоже пицца, поэтому самая верхняя начинка будет представлять всю композицию. Список ингредиентов такой пиццы представляет собой список ингредиентов базовой пиццы плюс название самой верхней начинки. Аналогично общая цена.

public class PizzaTopping extends PizzaIngredient implements Pizza {
    public PizzaTopping(String name, Pizza pizza) {
        this.name = name;
        this.pizza = pizza;
    }

    public String getIngredients() {
        return pizza.getIngredients() + ", " + getName();
    }
    public double getTotalPrice() {
        return pizza.getTotalPrice() + getPrice();
    }
    public String getName() {
        return name;
    }

    private final String name;
    private final Pizza pizza;
}

Давайте определим некоторые конкретные начинки:

public class MozzarellaTopping extends PizzaTopping {
    public MozzarellaTopping(Pizza pizza) {
        super("mozzarella", pizza);
    }

    private double getPrice() {
        return 0.5;
    }
}

public class MushroomTopping extends PizzaTopping {
    public MushroomTopping(Pizza pizza) {
        super("mushroom", pizza);
    }

    private double getPrice() {
        return 2.0;
    }
}

public class PepperoniTopping extends PizzaTopping {
    public PepperoniTopping(Pizza pizza) {
        super("pepperoni", pizza);
    }

    private double getPrice() {
        return 1.5;
    }
}

public class GreenOliveTopping extends PizzaTopping {
    public GreenOliveTopping(Pizza pizza) {
        super("green olive", pizza);
    }

    private double getPrice() {
        return 1.2;
    }
}

Хорошо, это много классов; но какие из них и когда нам понадобятся?

Здесь Фабрика присоединяется к команде. Фабрика — это класс для создания объектов некоторых классов. Он используется, чтобы скрыть детали создания за кулисами, особенно когда создаваемые объекты сложны или относятся к разным конкретным классам. Когда результирующие объекты создаются как автономные объекты, фабричный класс может быть просто пространством имен со статическим методом в нем. OTOH, если объекты создаются в некотором контексте, фабрика может быть объектом, связанным с контекстом (например, параметризованным) и использующим этот контекст в процессе создания.

Мы можем использовать фабрику для создания ингредиентов для пиццы по желанию пользователя. Большинство ингредиентов — это начинки, которые необходимо нанести поверх уже существующей пиццы, поэтому давайте передадим пиццу на фабрику, чтобы в результате получить украшенную пиццу. Особый случай — создание основы для пиццы, которая не наносится на другую пиццу; в этом случае параметр pizza игнорируется, поэтому мы можем передать null.

public class PizzaFactory {
    public static Pizza getPizza(Pizza pizza, String name)
    {
        if ( name.equals("small") || name.equals("medium") || name.equals("large") )
            return new PizzaBase(name);
        else if ( name.equals("mozzarella") )
            return new MozzarellaTopping(pizza);   // add topping to the pizza
        else if ( name.equals("mushroom") )
            return new MushroomTopping(pizza);
        else if ( name.equals("pepperoni") )
            return new PepperoniTopping(pizza);
        else if ( name.equals("green olive") )
            return new GreenOliveTopping(pizza);

        return null;
    }
}

Теперь мы готовы построить нашу пиццу.

class PizzaTest {
    public static void main(String[] args) {
        DecimalFormat priceFormat = new DecimalFormat("#.##");

        Pizza pizza;

        pizza = PizzaFactory.getPizza(null, "small");
        System.out.println("The small pizza is: " + pizza.getIngredients());
        System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));

        pizza = PizzaFactory.getPizza(null, "medium");
        pizza = PizzaFactory.getPizza(pizza, "mozzarella");
        pizza = PizzaFactory.getPizza(pizza, "green olive");

        System.out.println("The medium pizza is: " + pizza.getIngredients());
        System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));

        String largePizzaOrder[] = { "large", "mozzarella", "pepperoni",
                                     "mushroom", "mozzarella", "green olive" };

        pizza = null;
        for (String cmd : largePizzaOrder)
            pizza = PizzaFactory.getPizza(pizza, cmd);

        System.out.println("The large pizza is: " + pizza.getIngredients());
        System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));
    }
}

Предупреждение: в приведенном выше коде есть несколько подводных камней и ярлыков.

Наиболее важным является отсутствие проверки ввода: при поступлении неожиданной команды фабрика вернет null, что вызовет сбой при будущем использовании getIngredients() или getTotalCost().

Другой — жесткое кодирование цен в конкретные классы. Фактическое решение должно было бы использовать некоторый прайс-лист и извлекать цены либо при создании ингредиента (и сохранять извлеченные цены в объектах ингредиентов), либо при использовании, то есть в методе getCost() (который потребует некоторого доступа к прайс-листу из пиццы). ингредиенты).

Некоторые связанные вопросы: Когда бы вы использовали шаблон Builder? и Начинка для пиццы с орнаментом Java. также см. мой ответ на Добавление состояния в шаблон декоратора для чего-то другого.

Другие вопросы по теме