当前位置: 首页 > 工具软件 > PHP Mushroom > 使用案例 >

工厂模式 披萨_使用装饰器模式构建PHP Pizza

施赞
2023-12-01

工厂模式 披萨

Introduction

介绍

This article shows how to exploit the object-oriented design pattern called Decorator Pattern to solve a common problem in application design.

本文介绍如何利用称为

To illustrate the problem, we will try to make a pizza kitchen ordering system.  There will be a-la-carte ingredients for our pizzas, and our customers can choose any combination of ingredients they want.  A pizza can be plain, or have any combination of toppings.  How many PHP classes would we need to describe the possible orders?  Let's say we have two toppings - pepperoni and mushroom.  Here are the pizzas our customers can order:

为了说明问题,我们将尝试制作一个比萨厨房点餐系统。 我们的披萨将采用单点配料,我们的客户可以选择他们想要的配料的任意组合。 披萨可以是普通的,也可以是浇头的任意组合。 我们需要多少个PHP 来描述可能的顺序? 假设我们有两个浇头-意大利辣香肠和蘑菇。 以下是我们的客户可以订购的披萨:

  • Plain

    平原
  • with Pepperoni

    与意大利辣香肠
  • with Mushroom

    蘑菇
  • with Pepperoni and Mushroom

    用意大利辣香肠和蘑菇
<?php
Class Plain { /* Plain Pizza Definition: $10 */ }
Class Pepperoni { /* Pepperoni Pizza Definition: $13 */ }
Class Mushrooms { /* Mushrooms Pizza Definition: $12 */ }
Class PepperoniAndMushrooms { /* Pepperoni + Mushrooms Pizza Definition: $15 */ }
/* Use the classes here */

With Growth, We Encounter Troubles

随着增长,我们遇到麻烦

But what if we add Mozzarella to the list?  Our problem gets a bit bigger!

但是,如果我们将Mozzarella添加到列表中呢? 我们的问题变得更大了!

  • Plain

    平原
  • with Pepperoni

    与意大利辣香肠
  • with Mushroom

    蘑菇
  • with Mozzarella

    配芝士
  • with Pepperoni, Mushroom

    与意大利辣香肠,蘑菇
  • with Pepperoni, Mozzarella

    配意大利辣香肠,马苏里拉
  • with Mushroom, Mozzarella

    配蘑菇,马苏里拉
  • with Pepperoni, Mushroom, Mozzarella

    与意大利辣香肠,蘑菇,芝士

But what if we add Green Pepper to the list?  Our problem is getting out of hand!

但是,如果我们将青椒添加到列表中怎么办? 我们的问题变得一发不可收拾!

  • Plain

    平原
  • with Pepperoni

    与意大利辣香肠
  • with Mushroom

    蘑菇
  • with Mozzarella

    配芝士
  • with Green Pepper

    青椒
  • with Pepperoni, Mushroom

    与意大利辣香肠,蘑菇
  • with Pepperoni, Mozzarella

    配意大利辣香肠,马苏里拉
  • with Pepperoni, Green Pepper

    与意大利辣香肠,青椒
  • with Mushroom, Mozzarella

    配蘑菇,马苏里拉
  • with Mushroom, Green Pepper

    配蘑菇,青椒
  • with Mozzarella, Green Pepper

    配芝士,青椒
  • with Pepperoni, Mushroom, Mozzarella

    与意大利辣香肠,蘑菇,芝士
  • with Pepperoni, Mushroom, Green Pepper

    与意大利辣香肠,蘑菇,青椒
  • with Pepperoni, Mozzarella, Green Pepper

    用意大利辣香肠,马苏里拉奶酪,青椒
  • with Mushroom, Mozzarella, Green Pepper

    配蘑菇,芝士,青椒
  • with Pepperoni, Mushroom, Mozzarella, Green Pepper

    与意大利辣香肠,蘑菇,芝士,青椒
combinations by a 2^n rate! 组合以2 ^ n的速率增长

In reality, the problem is much more severe than these simple examples.  Pizzas can have lots of different toppings.

实际上, 这个问题比这些简单的例子严重得多。 披萨可以有很多不同的浇头

If you only serve the top-10 pizza items, your clients can order more than 5,000 different combinations, and that's when they choose only four toppings.  Move up to the top-50 pizza items, and you're looking at a huge programming problem -- more than 5,500,000 different pizzas can be made with just four toppings from that list.

如果您只提供前10个披萨项目,则客户可以订购5,000多种不同的组合,那时他们只能选择四个浇头。 转到排名前50位的披萨项目,您就会遇到一个巨大的编程问题-仅用该列表中的四个浇头就可以制作超过5,500,000种不同的披萨。

You just can't write that many PHP classes, so it's clear that a different solution is needed.  What may not be immediately clear, however, is the impact of a price change on our programming.  If just one of the 50 ingredients changed price, it has the potential to affect more than 100,000 different pizza classes.  If you could change one class per minute, you would be looking at nearly 70 days of non-stop coding just to change the price of an ingredient.  We cannot have one PHP class for every pizza.

您只是不能编写那么多PHP类,因此很显然需要一个不同的解决方案。 然而,可能尚不清楚的是价格变化对我们编程的影响。 如果仅改变50种食材中的一种,它就有可能影响超过100,000种不同的比萨饼。 如果您每分钟可以更改一个班级,那么您将需要花费近70天的不间断编码来改变一种成分的价格。 我们不能为每个披萨提供一个PHP类。

Can We Use Subclasses and Inheritance?

我们可以使用子类和继承吗?

Could we handle this problem with inheritance or with multiple methods on a parent class?  Perhaps, and it's an instructive exercise to try that.  You will probably find yourself writing a great many if() statements.  And there is the question of inheritance - what method would you override to adjust the price?  It may be difficult to test the code.  Add in an order for a coke or a beer and see what happens to the inheritance design.  Change a price and you have to change a method on the parent class or a method on a child class, and that implies a new round of integration testing.  And these kinds of changes must happen in the source code at compile time.

我们可以通过继承或父类上的多个方法来解决此问题吗? 也许,尝试这样做是很有启发性的。 您可能会发现自己编写了很多if()语句。 还有继承的问题-您将使用哪种方法来调整价格? 测试代码可能很困难。 依次订购可乐或啤酒,看看继承设计会发生什么。 更改价格,您必须更改父类上的方法或子类上的方法,这意味着需要进行新一轮的集成测试。 并且这些更改必须在编译时在源代码中发生。

If you're into the formal principles of object-oriented design, you may see that this violates the Single Responsibility Principle, since every object (pizza) created from a master class must carry with it all of the methods to create all of the possible combinations of toppings - whether they are needed on this pizza or not.  There is no Separation of Concerns, thus the resulting software contains unnecessary parts.

如果您对面向对象设计的正式原则有所了解,您可能会发现这违反了“ 单一职责原则” ,因为从主类创建的每个对象(比萨饼)都必须带有所有创建所有可能方法的方法。浇头的组合-是否需要在此披萨上使用。 没有关注点分离 ,因此生成的软件包含不必要的部分。

Decorator Pattern to the Rescue

救援人员的装饰模式

The classic definition of the Decorator Pattern is that the pattern attaches responsibilities to an object dynamically.  This means that the interface to the object is stable and unchanged, and software that uses the undecorated object can remain intact.  But by adding the decoration (just like the pizza topping) we get a different object.  In the case of our code example, we will decorate the pizza orders by adding a topping and a cost to the pizza as it is built in our object-oriented kitchen.

装饰器模式的经典定义是模式动态地将责任附加到对象上。 这意味着到对象的接口是稳定且不变的,并且使用未修饰对象的软件可以保持不变。 但是通过添加装饰(就像披萨顶部一样),我们得到了一个不同的对象。 在我们的代码示例中,我们将通过在面向对象的厨房中构建披萨添加浇头和成本来装饰披萨订单。

To start with, we will need an interface that defines the way the our Pizza will look to the rest of the code.  Appropriately we will call this interface, "Pizza."  The interface defines two methods that any implementing class must define.

首先,我们需要一个接口 ,该接口定义比萨饼在其余代码中的外观。 适当地,我们将此接口称为“比萨饼”。 该接口定义了任何实现类都必须定义的两个方法。

Interface Pizza
{
    public function topping();
    public function price();
}

Next we will define an abstract class that implements the topping() method, but leaves the cost() method to the subclasses that extend it.  We are playing a bit of a trick with this software to cut down on the number of variables and lines of code we will need.  Take a look at the return from the concrete topping() method.  It returns an array that contains whatever was in the topping array before it was called, and adds its own class name to the array.  This lets us name our subclasses with a name that identifies the topping.  That may sound strange at first, but it will make much more sense as you see how we use the interface and the abstract class to build pizzas.  You will soon see that this scheme means that we write less code and therefore have less chance for error!

接下来,我们将定义一个实现topping()方法的抽象类 ,但将cost()方法留给扩展它的子类。 我们在使用该软件方面花了点时间才能减少我们所需的变量和代码行数。 看一下具体的 topping()方法的收益。 它返回一个数组,其中包含调用前顶部数组中的所有内容, 并将其自己的类名添加到该数组中。 这使我们可以使用标识顶部的名称来命名子类。 乍一看这听起来很奇怪,但是当您看到我们如何使用接口和抽象类来构建披萨时,这将更加有意义。 您很快就会看到,该方案意味着我们编写的代码更少,因此出错的机会也更少!

Abstract Class Topping
{
    public function topping(){
        return array_merge($this->Pizza->topping(), [get_class($this)]);
    }

    abstract public function price();
}

Armed with this code set, we are ready to make a pizza.  Our BasicPizza class will do that for us, laying down a delicious crust.  Note that it implements the Pizza interface, which means that it must produce both of the methods called for in the interface definition.  So in addition to the delicious crust, it also establishes the base price for our pizza, in this case, $10.  If we wanted to, we could sell the BasicPizza as-is.  Just "new up" a BasicPizza object and you can see that it is fully formed, but also full of potential for us to add toppings.

有了此代码集,我们准备制作披萨。 我们的BasicPizza类将为我们做到这一点,铺上一层美味的面包皮。 请注意,它实现了Pizza接口,这意味着它必须产生接口定义中要求的两种方法。 因此,除了美味的外皮外,它还确定了我们的披萨的底价,在本例中为10美元。 如果我们愿意,我们可以按原样出售BasicPizza。 只需“新建”一个BasicPizza对象,您就可以看到它已完全形成,但是对于我们添加浇头也很有潜力。

Class BasicPizza implements Pizza
{
    public function topping(){
        return ['Delicious Crust'];
    }

    public function price(){
        return [10];
    }
}

Class Inheritance and Implementation

类的继承与实现

Now comes the fun part -- we add toppings to our pizza by calling each topping class and injecting the previously built pizza object into its constructor.  Each of the concrete "topping" classes extends the abstract class Topping and implements the Pizza interface.  Because these concrete classes (the classes that extend the abstract class) conform to the Pizza interface, they must have two methods, topping() and price().  Because they provide a concrete implementation of the abstract class Topping, they inherit the topping() method from that class, and must provide their own concrete method for the abstract method price().  It is through the use of this concrete price() method that we are able to set the price for each topping individually, and exactly once in the entire code set.

现在来了有趣的部分-我们通过调用每个topping类并将先前构建的Pizza对象注入到其构造函数中来为比萨添加浇头。 每个具体的“ topping”类都扩展了抽象类Topping并实现了Pizza接口。 因为这些具体类(扩展抽象类的类)符合Pizza接口,所以它们必须具有topping()和price()两个方法。 因为它们提供了抽象类Topping的具体实现,所以它们从该类继承 topping()方法,并且必须为抽象方法price()提供自己的具体方法。 通过使用这种具体的price()方法,我们能够分别为每个打顶设置价格,而在整个代码集中恰好设置一次。

As you can see, our RedSauce Class looks similar to the BasicPizza class, but it does not implement the topping() method -- it inherits the method from the abstract class Topping that it extends.  The other classes that add toppings to the BasicPizza all follow the same general design.  After RedSauce, we can include more classes of the same design.  Note also, that red sauce is free (the price is zero), whereas the basic pizza and the other toppings have an associated price.

如您所见,我们的RedSauce类看起来类似于BasicPizza类,但是它没有实现topping()方法-它继承了它扩展的抽象类Topping中的方法。 其他向BasicPizza添加浇头的类均遵循相同的常规设计。 在RedSauce之后,我们可以包含更多相同设计的类。 还要注意,红酱是免费的(价格为零),而基本的比萨饼和其他馅料的价格是相关的。

Class RedSauce extends Topping implements Pizza
{
    public function __construct(Pizza $pie){
        $this->Pizza = $pie;
    }
    public function price(){
        return array_merge($this->Pizza->price(), [0]);
    }
}

To wrap everything up and complete our order, we have the CheckOut class.  It collapses the array of toppings we have built and adds up the total price for the pizza.  These data points are available in two method calls on the object.

为了包装一切并完成订单,我们提供CheckOut类。 它折叠了我们构建的一系列浇头,并增加了比萨的总价格。 这些数据点在对象的两个方法调用中可用。

Class Checkout
{
    public function __construct(Pizza $pie){
        $this->Pizza = $pie;
    }
    public function totalPrice(){
        return array_sum($this->Pizza->price());
    }
    public function allToppings(){
        return implode(', ', $this->Pizza->topping());
    }
}

Using our Decorator Pattern in PHP Code

在PHP代码中使用我们的装饰器模式

Now that we have all of the essential parts defined, we can call for a pizza with the single line of code shown below.  You read the classes from right to left, since this is how they are encapsulated in parentheses, and how the references will be resolved by PHP.

现在,我们已经定义了所有基本部分,我们可以使用下面显示的单行代码来调用披萨。 您从右到左阅读这些类,因为这是将它们封装在括号中的方式,以及如何通过PHP解析引用。

$pizza = new Checkout(new Mushrooms(new Pepperoni(new RedSauce(new BasicPizza))));
// OUTPUT: For 15.00, you can get a pizza with Delicious Crust, RedSauce, Pepperoni, Mushrooms

If you're like me, that expression feels a little awkward, so I prefer to write this code out on separate lines, like this.  The result is going to be correct either way.

如果您像我一样,该表达式会有些尴尬,所以我更喜欢像这样在单独的行上编写此代码。 两种方法的结果都是正确的。

$pizza = new BasicPizza;
$pizza = new RedSauce($pizza);
$pizza = new Pepperoni($pizza);
$pizza = new Mushrooms($pizza);
$pizza = new Mozzarella($pizza);
$pizza = new Checkout($pizza);
// OUTPUT: For 16.50, you can get a pizza with Delicious Crust, RedSauce, etc...

Using the Decorator Pattern with HTML Forms and PHP Variables

将装饰器模式与HTML表单和PHP变量一起使用

There is another advantage of writing the class instantiations on separate lines.  We can use a variable name to identify the classes.  This means that our design can play well with an HTML form, and our clients can order pizza over the web!  Here is the complete script, allowing for us to receive a pizza order with the client's choice of toppings.  Obviously, you would add security measures to something like this before you released it into the wild, but even without that, it demonstrates the design pattern.  You can see it in action here:

在单独的行上编写类实例的另一个优点是。 我们可以使用变量名来标识类。 这意味着我们的设计可以很好地使用HTML表单,并且我们的客户可以通过网络订购比萨饼! 这是完整的脚本,使我们可以根据客户选择的浇头接收比萨订单。 显然,在将其发布到野外之前,您应该为此类添加安全措施,但是即使没有这样做,它也可以演示设计模式。 您可以在此处查看其运行情况:

http://iconoun.com/demo/formdecorator.php http://iconoun.com/demo/formdecorator.php
<?php // formdecorator.php

/**
 * Demonstrate a run-time decorator pattern
 * See https://laracasts.com/lessons/the-decorator-pattern
 */
error_reporting(E_ALL);
echo '<pre>';


// OUR BASIC INTERFACE
Interface Pizza
{
    public function topping();
    public function price();
}


// OUR ABSTRACT CLASS
Abstract Class Topping
{
    public function topping(){
        return array_merge($this->Pizza->topping(), [get_class($this)]);
    }

    abstract public function price();
}


// OUR BASIC CLASS
Class BasicPizza implements Pizza
{
    public function topping(){
        return ['Delicious Crust'];
    }

    public function price(){
        return [10];
    }
}


// OUR TOPPING CLASSES
Class RedSauce extends Topping implements Pizza
{
    public function __construct(Pizza $pie){
        $this->Pizza = $pie;
    }
    public function price(){
        return array_merge($this->Pizza->price(), [0]);
    }
}

Class Pepperoni extends Topping implements Pizza
{
    public function __construct(Pizza $pie){
        $this->Pizza = $pie;
    }
    public function price(){
        return array_merge($this->Pizza->price(), [3]);
    }
}

Class Mushrooms extends Topping implements Pizza
{
    public function __construct(Pizza $pie){
        $this->Pizza = $pie;
    }
    public function price(){
        return array_merge($this->Pizza->price(), [2]);
    }
}

Class Mozzarella extends Topping implements Pizza
{
    public function __construct(Pizza $pie){
        $this->Pizza = $pie;
    }
    public function price(){
        return array_merge($this->Pizza->price(), [1.5]);
    }
}


// OUR FINAL CLASS
Class Checkout
{
    public function __construct(Pizza $pie){
        $this->Pizza = $pie;
    }
    public function totalPrice(){
        return array_sum($this->Pizza->price());
    }
    public function allToppings(){
        return implode(', ', $this->Pizza->topping());
    }
}


// DO WE HAVE A POST-METHOD REQUEST?
if (!empty($_POST['toppings']))
{
    // START OUR PIZZA
    $pizza = new BasicPizza;

    // ADD OUR TOPPINGS
    foreach ($_POST['toppings'] as $topping => $nothing)
    {
        $pizza = new $topping($pizza);
    }
    // FINISH OUR PIZZA AND REPORT
    $pizza = new Checkout($pizza);

    $p = number_format( $pizza->totalPrice(), 2 );
    $t = $pizza->allToppings();
    echo "For $p, you can get a pizza with $t" . PHP_EOL;
}


// CREATE THE FORM TO ORDER A PIZZA
$form = <<<EOD
<form method="post">
Our Pizzas all have delicious crust!  Choose your toppings here:
<input type="checkbox" name="toppings[RedSauce]"   />Red Sauce
<input type="checkbox" name="toppings[Pepperoni]"  />Pepperoni
<input type="checkbox" name="toppings[Mushrooms]"  />Mushrooms
<input type="checkbox" name="toppings[Mozzarella]" />Mozzarella
<input type="submit" value="Order Now!" />
</form>
EOD;

echo $form;

Summary

摘要

In this article we have shown the risks that arise when a great many configuration options exist in a system, and we have shown how the Decorator Pattern can be used in PHP to give us a clean, maintainable code set for dealing with what would otherwise be great complexity.

在本文中,我们展示了系统中存在大量配置选项时所产生的风险,并且展示了如何在PHP中使用Decorator Pattern来为我们提供一个干净,可维护的代码集,以处理原本可能的情况。非常复杂。

Please give us your feedback!

请给我们您的反馈意见!

If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!

如果您发现本文有帮助,请单击下面的“竖起大拇指”按钮。 这样做可以使EE社区了解对EE成员有价值的内容,并为将来的文章提供指导。 如果您有任何问题或意见,请添加。 谢谢!

翻译自: https://www.experts-exchange.com/articles/18300/Building-a-PHP-Pizza-with-the-Decorator-Pattern.html

工厂模式 披萨

 类似资料: