【问题标题】:Decorator design and factory design patterns装饰器设计和工厂设计模式
【发布时间】:2019-03-28 04:05:29
【问题描述】:

我试图弄清楚如何使用用户输入并根据用户输入输出某些信息。有人可以提供一个简单的例子,例如在一个简单的披萨店中使用装饰器模式吗?

所以我知道如何使用装饰器模式,只是用户输入部分让我卡住了。所以假设用户想要做一个披萨,他们会首先选择披萨的大小,然后根据需要添加许多 ASB 配料。然后当他们完成后,他们将看到他们添加的总价格以及他们添加的总价格(如收据)。这是在 Java 中。

【问题讨论】:

    标签: java design-patterns


    【解决方案1】:

    让我们从以下几点开始

    1. 最纯粹形式的装饰器模式旨在enhance existing behavior of an object at run time without destroying the existing interface of the object
    2. 装饰意味着增强对象的现有行为。
    3. 装饰对象与被装饰的基本对象具有相同的(基本)接口。

    问题:一个对象是从它的编译时类派生的。现在你将如何继续增强行为?

    答案:通过使用装饰器模式,也称为包装器。

    示例:您有一个可以加密的文件,假设当前加密方法是 5,结果将是加密文件。加密文件可以再次加密。另外让我们假设有 5 种压缩文件的方法,以后也可以增加。一个文件可以用methodEA加密然后可以用MethodZA压缩然后再用methodEB加密,相似的序列可以产生不同的结果文件。

    一个好方法如下。

    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 的结构。

    【讨论】:

      【解决方案2】:

      装饰器是一个扩展另一个类的功能的类。装饰器通常实现相同的接口,以便可以使用装饰对象而不是基本对象。一个很好的例子是压缩器和/或加密器应用于文件或更一般地应用于数据流实现,如@nits.kkthe answer 所示。

      以披萨为例,我们应该定义我们需要的行为:

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

      比萨饼由两种主要成分组成:强制性的单一基料和可选的多种配料。每种成分都有自己的价格。

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

      pizza base 本身就是一个最简单的pizza,所以它必须实现Pizza 接口。它有一个大小作为它的属性(当然还有价格)。我们可以将 size 实现为一个单独的类,但我认为它不合理 - 它不够通用,无法在比萨世界之外有用,也不够复杂,不值得拥有自己的接口。

      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() 方法中(这需要从比萨的配料)。

      【讨论】:

        【解决方案3】:

        从概念上讲,在装饰器模式中,一个处理的输出作为另一个处理的输入。

        所以在你的情况下,应该是这样的:

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

        解析为:

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

        此外,您可以定义一个工厂类来获取各种大小的对象比萨饼,例如标准、中、大。无论您选择哪种语言,逻辑都是相同的。

        【讨论】:

          【解决方案4】:

          您对装饰器模式的目的的期望/理解可能略有偏差。装饰器模式旨在包装现有的功能集,以便提供一些新的功能,以及已经存在的功能。

          一个更好的比萨示例是参加一个可以做以下事情的比萨课:

          • 提供比萨饼
          • 供应软饮料

          然后尝试添加可以提供沙拉的功能。所以这个披萨类的简化版本可能如下所示:

          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("你点了披萨"); } 公共字符串 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 已经拥有的相同功能。但是,它也添加了一些自己的新功能。

          装饰器设计模式在以下情况下很有用)。在这种情况下,包装该类并使用装饰器模式是一个不错的选择。

          【讨论】:

          • 好点,但我会注意到 OP 的特定示例本质上来自“Head First Design Patterns”及其对咖啡店的讨论。
          • @KevinO 好吧,看来我已经先跳入错误的答案了哈哈:P
          【解决方案5】:

          一些相关问题:When would you use the Builder Pattern?Java Decorative Pattern Pizza Topping。另请参阅我对Adding State in Decorator Pattern 的回答,了解不同的情况。

          【讨论】:

            猜你喜欢
            • 2010-10-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-11-28
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多