简体   繁体   English

装饰设计和工厂设计模式

[英]Decorator design and factory design patterns

I am trying to figure out how to use the users input and output certain information depending on the user input. 我试图弄清楚如何使用用户输入并根据用户输入输出某些信息。 Can someone provide a simple example of using the decorator pattern for a simple pizza shop for example? 有人可以提供一个简单的示例,例如在一个简单的比萨店中使用装饰器模式吗?

So I know how to go about the decorator pattern, it's just the user input portion that has me stuck. 因此,我知道如何处理装饰器模式,只是用户输入部分使我受了困扰。 So let's say the user wants to make a pizza, they will first choose the size of pizza, and then add ASB many toppings as they want. 假设用户想要制作披萨,他们将首先选择披萨的大小,然后根据需要添加ASB多种配料。 Then when they have finished, they will see their total price for what they added as well as what they added (like a reciept). 然后,当他们完成时,他们将看到所添加内容以及所添加内容(例如收据)的总价。 This is in Java. 这是用Java编写的。

Lets see below points to start with 让我们从以下几点开始

  1. Decorator pattern in its purest form intends to enhance existing behavior of an object at run time without destroying the existing interface of the object . 最纯粹形式的装饰器模式旨在enhance existing behavior of an object at run time without destroying the existing interface of the object
  2. Decoration implies enhancement of existing behaviour of an object. 装饰意味着增强对象的现有行为。
  3. Decorated object has the same (base) interface as of the basic object being decorated. 装饰的对象具有与要装饰的基本对象相同的(基本)接口。

Question : An object is derieved from its class which is compile time. 问题: 对象从其类(编译时)中删除。 Now how would you go on enhancing the behaviour ? 现在,您将如何继续改善行为?

Answer : By using Decorator pattern also known as wrapper. 答: 通过使用装饰器模式(也称为包装器)。

Example : You have a file which can be encrypted, lets say methods of encryption are currently 5, the result will be encrypted file. 示例:您有一个可以加密的文件,可以说当前的加密方法为5,结果将是加密文件。 Encrypted file can again be encrypted. 加密的文件可以再次加密。 Additionally lets assume there are 5 ways to zip the file which can later increase also. 另外,假设有5种压缩文件的方式,以后也可以增加。 A file can be encrypted by methodEA then can be zipped by MethodZA then again can be encrypted by methodEB, similar sequences can make different result files. 可以使用methodEA加密文件,然后使用MethodZA压缩文件,然后再次使用methodEB加密文件,相似的序列可以生成不同的结果文件。

One of the good way is as below. 好的方法之一如下。

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();
}

In above example code it can be said 在上面的示例代码中,可以说

An object of TextFile has been decorated (by wrapping) by AEncryptedFile object whichis further decorated by BZippedFile, in each of these decoration additional enhancement has been made to existing object TextFile的对象已由AEncryptedFile对象修饰(通过包装),而BEncryptedFile对象由BZippedFile进一步修饰,在每种修饰中,对现有对象进行了附加增强

This way existing object of TextFile can be passed to various methods at runtime and the object can be decorated by wrapping it in another object of subtype of TextFile. 这样,TextFile的现有对象可以在运行时传递给各种方法,并且可以通过将其包装在TextFile子类型的另一个对象中来装饰该对象。

Note : Decorator pattern implementation has a structure of a LinkedList. 注意 :装饰器模式实现具有LinkedList的结构。

A decorator is a class that extends the functionality of another class. 装饰器是扩展另一类功能的类。 A decorator usually implements the same interface so that a decorated object can be used instead of the basic one. 装饰器通常实现相同的接口,以便可以使用装饰对象而不是基本对象。 A good example is a compressor and/or encrypter applied over a file or, more generally, over a data stream implementation, as shown in the answer by @ nits.kk . 一个很好的例子是在文件上或更普遍地在数据流实现上应用的压缩器和/或加密器,如@ nits.kk 的答案所示。

In a case of pizza, we should define what behavior we need: 对于披萨,我们应该定义我们需要的行为:

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

A pizza consists of two main types of ingredients: a mandatory single base and optional multiple toppings. 比萨饼包含两种主要类型的食材:强制性单一底料和可选的多种配料。 Each ingredient has its own price. 每种成分都有自己的价格。

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

The pizza base is itself a simplest possible pizza, so it must implement the Pizza interface. 比萨饼本身就是最简单的比萨饼,因此必须实现Pizza接口。 It has a size as its attribute (and the price, of course). 它具有大小作为属性(当然还有价格)。 We could implement a size as a separate class, but I don't consider it reasonable - it's not general enough to be useful outside the pizza universe and not complex enough to deserve its own interface. 我们可以将一个大小实现为一个单独的类,但我认为这不合理-它的一般性不足以在披萨世界之外有用,并且不够复杂以至于不应该拥有自己的接口。

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;
}

Now we need toppings. 现在我们需要浇头。 They wil be added on top of pizza as decorators: a pizza plus a topping is a pizza, too, so the topmost topping will represent the whole composition. 它们将被添加到披萨上作为装饰物:披萨加浇头也是披萨,因此最上面的浇头将代表整个成分。 Such pizza's ingredients list is a list of ingredients of the underlying pizza plus the name of its topmost topping. 此类披萨的成分列表是基础披萨的成分列表以及其最上面的配料的名称。 Similarly the total price. 同样,总价。

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;
}

Let's define some concrete toppings: 让我们定义一些具体的浇头:

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;
    }
}

Okay, that's lots of classes; 好的,有很多课。 but which of them and when shall we need? 但是它们中的哪一个以及我们什么时候需要?

Here a Factory joins the team. 在这里,工厂加入了团队。 A factory is a class to create objects of some classes. 工厂是用于创建某些类的对象的类。 It's used to hide the creation details behind the scenes, especially when the objects created are complicated or are of different concrete classes. 它用于将创建细节隐藏在幕后,尤其是当创建的对象复杂或具有不同具体类时。 When resulting objects are created as standalone entities, the factory class can be just a namespace with a static method in it. 将结果对象创建为独立实体时,工厂类可以只是其中具有静态方法的名称空间。 OTOH, if objects are created in some context, the factory could be an object associated with the context (for example, parametrized) and using that context in creation process. OTOH,如果在某些上下文中创建对象,则工厂可以是与上下文关联的对象(例如,参数化),并在创建过程中使用该上下文。

We may use a factory to create pizza ingredients at will, according to user input. 根据用户输入,我们可能会使用工厂随意制作披萨配料。 Most ingredients are toppings, which must be applied on top of already existing pizza, so let's pass the pizza to a factory to receive the decorated pizza as a result. 大多数配料都是浇头,必须在已经存在的比萨饼上使用,因此让我们将比萨饼送到工厂以接收装饰好的比萨饼。 The special case is creating a pizza base, which is not applied on another pizza; 特殊情况是创建一个比萨饼基料,该基料不能应用于其他比萨饼; in this case the pizza parameter is ignored, so we may pass null . 在这种情况下, 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;
    }
}

Now we're ready to build our pizza. 现在,我们准备制作披萨。

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()));
    }
}

Warning: there are some pitfalls and shortcuts in the above code. 警告:以上代码中存在一些陷阱和快捷方式。

The most important is the lack of validation of input: when an unexpected command arrives, the factory will return null which will cause a crash on future use of getIngredients() or getTotalCost() . 最重要的是缺乏输入验证:当意外命令到达时,工厂将返回null ,这将在以后使用getIngredients()getTotalCost()时导致崩溃。

Another one is hard-coding prices into concrete classes. 另一个是将价格硬编码为具体类别。 An actual solution would have to use some price list and fetch prices either on an ingredient creation (and store fetched prices in the ingredient objects) or on use, ie in the getCost() method (which would require some access to the price list from pizzas' ingredients). 实际的解决方案将必须使用某些价格表,并在成分创建时将价格获取(并将获取的价格存储在成分对象中)或在使用时使用,即在getCost()方法中使用(这需要从中访问价格表)披萨的配料)。

Concept-wise, in decorator pattern, the output of one processing goes as input to another processing. 从概念上讲,在装饰器模式中,一个处理的输出将作为另一处理的输入。

So in your case, it should be like this: 因此,在您的情况下,应该是这样的:

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

which resolves to : 解析为:

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

Further, you can define a factory class to get Pizza of object of various sizes, say Standard, Medium, Large. 此外,您可以定义一个工厂类来获取各种尺寸(例如标准,中,大)对象的Pizza。 Logic is same irrespective of language you pick. 无论选择哪种语言,逻辑都是一样的。

Your expectation/understanding of the purpose of the decorator pattern might be slightly off. 您对装饰器模式用途的期望/理解可能会略有偏离。 The decorator pattern is intended to wrap an existing set of functionality in order to offer some new functionality, in addition to the functionality which is already there. 装饰器模式旨在包装现有功能集,以提供除已有功能之外的一些功能。

A better pizza example would be taking a pizza class which can do the following things: 一个更好的披萨示例将是一个披萨课,它可以做以下事情:

  • serve pizza 提供披萨
  • serve soft drinks 供应软饮料

and then trying to add functionality which can serve salads. 然后尝试添加可用于沙拉的功能。 So a simplified version of this pizza class might look like this: 因此,此披萨类的简化版本可能如下所示:

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

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

To implement the decorator pattern here, we wrap the existing Pizzeria class, and then add some new function public String orderPizza() { System.out.println("you ordered a pizza"); 为了在这里实现装饰器模式,我们包装了现有的Pizzeria类,然后添加了一些新函数public String orderPizza(){System.out.println(“您订购了披萨”); } public String orderSoftDrink() { System.out.println("you ordered a soft drink"); } public String orderSoftDrink(){System.out.println(“您点了一杯软饮料”); } ality: }:

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");
    }
}

The key point here is that the NewPizzeria class "owns" its own Pizzeria object. 这里的关键点是NewPizzeria类“拥有”自己的Pizzeria对象。 For the most part, it just rexposes the same functionality which Pizzeria already has. 在大多数情况下,它只是重新融合了Pizzeria已经拥有的功能。 But, it also adds some new functionality of its own. 但是,它也增加了一些新功能。

The decorator design pattern is useful in cases where a class already exists which mostly fits your needs, but you need something else and you also cannot rewrite that class (eg because it is part of some library). 装饰器设计模式在已经存在一个最适合您的类但已经需要其他东西并且您也不能重写该类的情况下很有用(例如,因为它是某些库的一部分)。 In this case, wrapping that class and using the decorator pattern is one good option. 在这种情况下,包装该类并使用装饰器模式是一个不错的选择。

Some related questions: When would you use the Builder Pattern? 一些相关的问题: 什么时候使用构建器模式? and Java Decorative Pattern Pizza Topping . Java装饰图案披萨馅料 also see my answer to Adding State in Decorator Pattern for something different. 另请参见我对在装饰器模式中添加状态的回答。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM