概述
装饰器模式(Decorator Pattern)是一种结构型设计模式,其核心思想是在不改变原有类结构和逻辑的前提下,通过动态添加额外的职责(功能)来扩展对象的行为。装饰器模式采用“组合优于继承”的设计原则,将核心功能与扩展功能分离,通过嵌套包装的方式灵活组合多种扩展功能,实现功能的动态增减和复用。
在装饰器模式中,所有装饰器类与被装饰的核心类遵循统一的接口规范,客户端可透明地使用被装饰后的对象,无需区分核心对象与装饰器对象,既保证了代码的灵活性和扩展性,又避免了继承带来的类爆炸问题。
核心价值
| 核心要点 | 详细说明 |
|---|---|
| 动态扩展功能 | 无需修改原有类代码,通过装饰器动态为对象添加或移除功能,扩展方式灵活,符合“开放-封闭原则” |
| 功能组合复用 | 多个装饰器可通过嵌套组合实现复杂功能,每个装饰器仅关注单一扩展职责,提升代码复用性和可维护性 |
| 替代继承优化 | 避免通过多层继承扩展功能导致的类数量激增(类爆炸),降低系统复杂度和耦合度 |
| 透明使用体验 | 装饰器与被装饰对象遵循同一接口,客户端可像使用原始对象一样使用装饰后的对象,无需感知装饰逻辑 |
适用场景
| 场景类型 | 具体说明 |
|---|---|
| 动态增减功能场景 | 如商品订单的优惠计算(满减、折扣、优惠券、积分抵扣可组合使用)、数据输出格式增强(压缩、加密、格式化可灵活搭配)等,需动态添加或移除功能 |
| 避免多层继承场景 | 当对象需要多种不同组合的扩展功能时(如文本处理需支持“加粗+斜体+下划线”“加粗+高亮”等多种组合),继承方式无法满足灵活组合需求,装饰器模式更合适 |
| 单一职责强化场景 | 每个扩展功能被封装为独立的装饰器类,职责边界清晰,便于单独维护、测试和扩展(如日志系统的“时间戳装饰”“日志分级装饰”“持久化装饰”分离) |
| 第三方组件扩展场景 | 无法修改第三方类源码(如框架组件、开源库),但需为其添加额外功能时,装饰器模式可无侵入式扩展 |
注意事项
| 注意点 | 具体说明 |
|---|---|
| 避免过度装饰 | 多层装饰器嵌套会增加对象调用链长度,可能导致系统性能损耗和调试难度提升,需合理控制装饰器层数 |
| 确保接口一致性 | 所有装饰器类必须与被装饰类实现同一接口,否则客户端无法透明使用,破坏模式的核心特性 |
| 区分核心功能与装饰功能 | 核心功能应封装在被装饰的原始类中,装饰器仅负责扩展功能,避免将核心逻辑写入装饰器导致职责混乱 |
| 避免装饰器依赖具体实现 | 装饰器应依赖抽象接口而非具体类,确保装饰器可复用在所有实现该接口的对象上,提升灵活性 |
完整代码
以“商品订单价格计算”为业务场景,构建装饰器模式实现:核心功能为计算商品原价,通过装饰器动态添加“满减优惠”“折扣优惠”“优惠券抵扣”等扩展功能,支持多种优惠组合使用。
<?php
declare(strict_types = 1);
/**
* 抽象组件接口:定义订单价格计算的统一规范
*/
interface OrderPrice
{
/**
* 计算最终价格
* @return float
*/
public function calculate(): float;
/**
* 获取价格描述(含优惠明细)
* @return string
*/
public function getDescription(): string;
}
/**
* 具体组件:原始订单(核心功能:计算商品原价)
*/
class OriginalOrder implements OrderPrice
{
/**
* 商品原价
* @var float
*/
private float $originalPrice;
public function __construct(float $originalPrice)
{
$this->originalPrice = $originalPrice;
}
public function calculate(): float
{
return $this->originalPrice;
}
public function getDescription(): string
{
return sprintf("商品原价:%.2f元", $this->originalPrice);
}
}
/**
* 抽象装饰器:实现组件接口,持有被装饰对象引用
*/
abstract class OrderDecorator implements OrderPrice
{
/**
* 被装饰的订单对象(可能是原始订单或其他装饰器)
* @var OrderPrice
*/
protected OrderPrice $order;
public function __construct(OrderPrice $order)
{
$this->order = $order;
}
// 子类需实现具体的计算逻辑和描述逻辑
abstract public function calculate(): float;
abstract public function getDescription(): string;
}
/**
* 具体装饰器1:满减优惠(满300减50)
*/
class FullReductionDecorator extends OrderDecorator
{
private const MIN_AMOUNT = 300.0; // 满减门槛
private const REDUCTION_AMOUNT = 50.0; // 减免金额
public function calculate(): float
{
// 先计算被装饰对象的价格,再叠加当前装饰器的优惠
$price = $this->order->calculate();
return $price >= self::MIN_AMOUNT ? $price - self::REDUCTION_AMOUNT : $price;
}
public function getDescription(): string
{
// 拼接被装饰对象的描述和当前装饰器的优惠信息
$baseDesc = $this->order->getDescription();
$price = $this->order->calculate();
$reductionDesc = $price >= self::MIN_AMOUNT
? sprintf(" + 满%.2f减%.2f", self::MIN_AMOUNT, self::REDUCTION_AMOUNT)
: "(未满足满减条件)";
return $baseDesc . $reductionDesc;
}
}
/**
* 具体装饰器2:折扣优惠(9折)
*/
class DiscountDecorator extends OrderDecorator
{
private const DISCOUNT_RATE = 0.9; // 折扣率
public function calculate(): float
{
return $this->order->calculate() * self::DISCOUNT_RATE;
}
public function getDescription(): string
{
$baseDesc = $this->order->getDescription();
return $baseDesc . sprintf(" + %.1f折优惠", self::DISCOUNT_RATE * 10);
}
}
/**
* 具体装饰器3:优惠券抵扣(固定抵扣20元)
*/
class CouponDecorator extends OrderDecorator
{
private const COUPON_AMOUNT = 20.0; // 优惠券金额
public function calculate(): float
{
$price = $this->order->calculate();
return $price > self::COUPON_AMOUNT ? $price - self::COUPON_AMOUNT : 0.0;
}
public function getDescription(): string
{
$baseDesc = $this->order->getDescription();
return $baseDesc . sprintf(" + 优惠券抵扣%.2f元", self::COUPON_AMOUNT);
}
}
// 测试代码
try {
// 1. 创建原始订单(商品原价400元)
$order = new OriginalOrder(400.0);
echo "原始订单:" . $order->getDescription() . " → 最终价格:" . $order->calculate() . "元<br/><br/>";
// 2. 仅添加满减优惠
$orderWithReduction = new FullReductionDecorator($order);
echo "满减优惠订单:" . $orderWithReduction->getDescription() . " → 最终价格:" . $orderWithReduction->calculate() . "元<br/><br/>";
// 3. 满减 + 折扣 组合优惠
$orderWithReductionAndDiscount = new DiscountDecorator($orderWithReduction);
echo "满减+折扣订单:" . $orderWithReductionAndDiscount->getDescription() . " → 最终价格:" . $orderWithReductionAndDiscount->calculate() . "元<br/><br/>";
// 4. 满减 + 折扣 + 优惠券 组合优惠
$orderWithAll = new CouponDecorator($orderWithReductionAndDiscount);
echo "全套优惠订单:" . $orderWithAll->getDescription() . " → 最终价格:" . $orderWithAll->calculate() . "元<br/>";
} catch (\Exception $e) {
echo "订单价格计算异常:" . $e->getMessage();
}
核心结构拆解
装饰器模式的核心由“抽象组件接口”“具体组件”“抽象装饰器”和“具体装饰器”四部分组成,结构层次清晰:
| 结构组成 | 详细说明 |
|---|---|
| 抽象组件接口(OrderPrice) | 定义被装饰对象和装饰器的统一行为规范(如 calculate getDescription),确保客户端可透明调用 |
| 具体组件(OriginalOrder) | 实现抽象组件接口,封装核心业务逻辑(如商品原价计算),是装饰器的装饰目标 |
| 抽象装饰器(OrderDecorator) | 实现抽象组件接口,持有被装饰对象(抽象组件类型)的引用,为具体装饰器提供统一的继承基础,可封装通用装饰逻辑 |
| 具体装饰器(FullReductionDecorator、DiscountDecorator 等) | 继承抽象装饰器,实现具体的扩展功能(如满减、折扣),在调用被装饰对象方法的基础上添加额外逻辑 |
关键优化点
| 优化方向 | 详细说明 |
|---|---|
| 接口统一约束 | 抽象组件接口严格定义核心方法,确保具体组件和所有装饰器的方法签名一致,客户端无需区分对象类型即可调用 |
| 组合式扩展 | 装饰器通过构造函数接收被装饰对象,支持多层嵌套组合(如“满减+折扣+优惠券”),实现功能的灵活叠加 |
| 无侵入扩展 | 原始组件类无需任何修改即可被扩展,装饰器仅通过组合方式附加功能,完全遵循“开放-封闭原则” |
| 职责单一明确 | 每个具体装饰器仅负责一项扩展功能(如满减装饰器只处理满减逻辑),符合“单一职责原则”,便于维护和扩展 |
常见错误规避
| 错误类型 | 问题描述及解决方案 |
|---|---|
| 装饰器未实现抽象组件接口 | 问题:装饰器未遵循统一接口,客户端无法透明使用装饰后的对象,导致类型错误。 解决:所有装饰器(含抽象装饰器)必须实现抽象组件接口,确保方法签名与核心组件一致 |
| 具体装饰器直接依赖具体组件 | 问题:装饰器构造函数接收具体组件类型而非抽象接口,导致装饰器无法复用在其他实现该接口的组件上。 解决:抽象装饰器和具体装饰器的构造函数参数应声明为抽象组件接口类型,依赖抽象而非具体 |
| 核心逻辑写入装饰器 | 问题:将原始组件的核心功能(如原价计算)写入装饰器,导致职责混乱,原始组件失去独立性。 解决:严格区分核心功能与扩展功能,核心逻辑仅保留在具体组件中,装饰器仅负责附加功能 |
| 装饰顺序错误导致逻辑异常 | 问题:不同装饰器组合时顺序不当(如先折扣后满减与先满减后折扣结果不同),导致业务逻辑异常。 解决:明确装饰器的执行顺序规则,或在上下文类中封装推荐的组合顺序,避免客户端误用 |
扩展场景示例(新增装饰器无需修改原有代码)
若业务新增“会员积分抵扣”功能(每100积分抵扣10元),仅需新增具体装饰器类,无需修改原有组件和装饰器,符合“开放-封闭原则”:
// 新增具体装饰器:会员积分抵扣
class PointDeductionDecorator extends OrderDecorator
{
private int $points; // 会员积分
public function __construct(OrderPrice $order, int $points)
{
parent::__construct($order);
$this->points = $points;
}
public function calculate(): float
{
$maxDeduction = floor($this->points / 100) * 10; // 每100积分抵扣10元
$price = $this->order->calculate();
return $price > $maxDeduction ? $price - $maxDeduction : 0.0;
}
public function getDescription(): string
{
$maxDeduction = floor($this->points / 100) * 10;
$baseDesc = $this->order->getDescription();
return $baseDesc . sprintf(" + 会员积分抵扣%.2f元(当前积分:%d)", $maxDeduction, $this->points);
}
}
// 测试新增装饰器(满减+折扣+优惠券+积分抵扣)
$orderWithPoints = new PointDeductionDecorator($orderWithAll, 500); // 500积分可抵扣50元
echo "<br/>全套优惠+积分抵扣:" . $orderWithPoints->getDescription() . " → 最终价格:" . $orderWithPoints->calculate() . "元<br/>";
// 输出结果:
// 全套优惠+积分抵扣:商品原价:400.00元 + 满300.00减50.00 + 9.0折优惠 + 优惠券抵扣20.00元 + 会员积分抵扣50.00元(当前积分:500) → 最终价格:242.00元
实际应用场景
| 场景分类 | 具体说明 |
|---|---|
| 数据处理增强 | 如API接口响应处理(数据格式化→压缩→加密→签名)、日志记录增强(基础日志→时间戳→分级标记→持久化存储),通过装饰器组合多种处理逻辑 |
| 电商营销活动 | 如订单优惠计算(满减、折扣、优惠券、积分抵扣等多种优惠组合)、商品价格修饰(原价→会员价→限时价→秒杀价),动态组合营销规则 |
| 框架扩展机制 | 如PHP框架中的中间件(Middleware)机制,请求处理流程通过多个中间件装饰(日志记录→权限校验→参数过滤→业务处理),实现请求流程的动态扩展 |
| UI组件增强 | 如网页UI组件装饰(基础按钮→加粗样式→高亮效果→点击事件→动画效果),通过装饰器为基础组件添加多种视觉和交互功能 |
装饰器模式与其他模式对比
| 对比维度 | 装饰器模式 | 策略模式 | 适配器模式 |
|---|---|---|---|
| 核心目标 | 动态添加扩展功能,组合多个职责 | 封装可替换算法,动态切换单一逻辑 | 转换接口,解决兼容性问题 |
| 对象关系 | 装饰器嵌套组合(包装关系) | 上下文依赖单一策略(关联关系) | 适配器包装适配对象(适配关系) |
| 执行逻辑 | 多层装饰器依次执行,叠加功能 | 选择一个策略单独执行,替换逻辑 | 适配器转换接口后调用适配对象方法 |
| 适用场景 | 功能动态扩展、多职责组合 | 算法动态切换、多规则选择 | 接口不兼容、第三方组件适配 |


