Menu
CoddyTech

装饰器模式

本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 77 课,共 91 课。

装饰器模式是一种结构型设计模式,它通过用称为装饰器的特殊包装对象包装对象,让你可以动态地为对象添加新行为。与在编译时添加行为的继承不同,装饰器会在运行时添加行为。

想象一家咖啡店。你先选一杯基础咖啡,然后添加牛奶、糖或鲜奶油等配料。

每种添加配料都会包裹原有的咖啡,并为其描述和价格增添内容。关键在于,基础咖啡和装饰器使用相同的接口。

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

装饰器实现相同的接口,并包装另一个 Coffee 对象:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();

输出:

Simple Coffee, Milk, Sugar = $2.75

每个装饰器都会包装前一个对象,形成一条链。这个模式让你可以按任意顺序组合任意数量的行为,而不必为每种可能的组合都创建一个类。想要双份牛奶?再包装一次就行。

challenge icon

挑战

简单

让我们使用装饰器模式构建一个披萨订购系统。你将从一个基础披萨开始,并动态添加配料:每种配料都会包装披萨,并为其描述和价格添加内容,就像本课中的咖啡示例一样。

你将把代码分成 4 个文件:

  • Pizza.php:定义一个 Pizza 接口,为所有披萨和配料规定统一规范。它应包含两个方法:getDescription(): string 和 getPrice(): float。
  • BasePizza.php:引入 Pizza 接口,并创建一个实现该接口的 MargheritaPizza 类。这是你的基础披萨,描述为 "Margherita",价格为 8.00。
  • Toppings.php:引入 Pizza 接口,并创建 3 个实现该接口的装饰器类。每个装饰器都会包装一个 Pizza 对象(在构造函数中接收该对象),并增加描述内容和价格:
    • CheeseTopping:在描述中添加 ", Extra Cheese",并将价格增加 1.50
    • PepperoniTopping:在描述中添加 ", Pepperoni",并将价格增加 2.00
    • MushroomTopping:在描述中添加 ", Mushrooms",并将价格增加 1.25
  • main.php:引入 BasePizza 和 Toppings 文件。你会收到一个输入:一个以逗号分隔的配料列表(例如 "cheese,pepperoni" 或 "mushroom,cheese,cheese")。

    从一个 MargheritaPizza 开始。对于输入中的每种配料,都用相应的装饰器包装披萨:

    • "cheese" 用 CheeseTopping 包装
    • "pepperoni" 用 PepperoniTopping 包装
    • "mushroom" 用 MushroomTopping 包装

    应用所有配料后,按以下格式输出最终披萨的描述和价格:

    [description] = $[price]

    将价格格式化为两位小数。

注意,同一种配料可以重复添加:点双倍奶酪,只需用 CheeseTopping 包装两次即可。这种灵活性正是装饰器模式的优势!

亲手试试

<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';

// 读取输入:用逗号分隔的配料列表
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);

// 从基础 MargheritaPizza 开始
$pizza = new MargheritaPizza();

// TODO: 遍历每种配料,用相应的装饰器包装披萨
// “cheese”:用 CheeseTopping 包装
// “pepperoni”:用 PepperoniTopping 包装
// “mushroom”:用 MushroomTopping 包装

// TODO: 按 [description] = $[price] 的格式打印结果
// 使用 number_format() 将价格格式化为保留 2 位小数

?>
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器