装饰器模式
本课属于 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每个装饰器都会包装前一个对象,形成一条链。这个模式让你可以按任意顺序组合任意数量的行为,而不必为每种可能的组合都创建一个类。想要双份牛奶?再包装一次就行。
挑战
简单让我们使用装饰器模式构建一个披萨订购系统。你将从一个基础披萨开始,并动态添加配料:每种配料都会包装披萨,并为其描述和价格添加内容,就像本课中的咖啡示例一样。
你将把代码分成 4 个文件:
Pizza.php:定义一个Pizza接口,为所有披萨和配料规定统一规范。它应包含两个方法:getDescription(): string和getPrice(): float。BasePizza.php:引入 Pizza 接口,并创建一个实现该接口的MargheritaPizza类。这是你的基础披萨,描述为"Margherita",价格为8.00。Toppings.php:引入 Pizza 接口,并创建 3 个实现该接口的装饰器类。每个装饰器都会包装一个Pizza对象(在构造函数中接收该对象),并增加描述内容和价格:CheeseTopping:在描述中添加", Extra Cheese",并将价格增加1.50PepperoniTopping:在描述中添加", Pepperoni",并将价格增加2.00MushroomTopping:在描述中添加", 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 位小数
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器