装饰设计和工厂设计模式

时间:2019-03-28 04:05:29

标签: java design-patterns

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

因此,我知道如何处理装饰器模式,这只是用户输入部分使我难以为继。假设用户想要制作披萨,他们将首先选择披萨的大小,然后根据需要添加ASB多种配料。然后,当他们完成时,他们将看到所添加内容以及所添加内容(例如收据)的总价。这是用Java编写的。

5 个答案:

答案 0 :(得分: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对象修饰(通过包装),而BEncryptedFile对象又被BZippedFile装饰,在每种修饰中,现有对象都进行了额外的增强

通过这种方式,TextFile的现有对象可以在运行时传递给各种方法,并且可以通过将其包装在TextFile子类型的另一个对象中来装饰该对象。

注意:装饰器模式的实现具有LinkedList的结构。

答案 1 :(得分:1)

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

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

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

匹萨由两种主要类型的食材组成:必选单一底料和可选多种配料。每种成分都有自己的价格。

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

比萨饼本身就是最简单的比萨饼,因此它必须实现Pizza接口。它具有大小作为属性(当然还有价格)。我们可以将大小作为一个单独的类来实现,但我认为这不合理-它不够通用,不足以在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 MozarellaTopping extends PizzaTopping {
    public MozarellaTopping(Pizza pizza) {
        super("mozarella", 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("mozarella") )
            return new MozarellaTopping(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, "mozarella");
        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", "mozarella", "pepperoni",
                                     "mushroom", "mozarella", "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()方法中使用(这需要对价格进行某些访问)比萨饼配料中列出)。

答案 2 :(得分:0)

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

因此,您的情况应该是这样的:

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

解析为:

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

此外,您可以定义工厂类以获取各种尺寸(例如标准,中,大)对象的Pizza。无论选择哪种语言,逻辑都是相同的。

答案 3 :(得分:0)

您对装饰器模式用途的期望/理解可能会略有偏离。装饰器模式旨在包装一组现有功能,以提供除已有功能之外的一些 new 功能。

一个更好的披萨示例将是一个披萨课,它可以做以下事情:

  • 送披萨
  • 提供汽水

,然后尝试添加可用于沙拉的功能。因此,此比萨饼类的简化版本可能如下所示:

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已经具有的功能。但是,它也增加了一些新功能。

装饰器设计模式在已经存在一个最适合您的类但已经需要其他 的情况下很有用,您也无法重写该类(例如,因为它是某些库的一部分) )。在这种情况下,包装该类并使用装饰器模式是一个不错的选择。

答案 4 :(得分:0)

一些相关的问题:When would you use the Builder Pattern?Java Decorative Pattern Pizza Topping。还可以看到我对Adding State in Decorator Pattern的回答是否有所不同。