结构型模式:适配器、装饰器、外观
使用结构型模式组合对象并简化接口。
结构型模式:适配器、装饰器、外观 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。
组合对象
结构型模式描述如何将类和对象组合成更大的结构,同时保持灵活性。本节介绍三种常用模式:
- 适配器——让不兼容的接口变得可用。
- 装饰器——无需继承即可添加行为。
- 外观——为复杂子系统提供简单的入口。
适配器:问题所在
您依赖一个PaymentGateway接口,但第三方 SDK 提供的是形式不同的chargeCard($amountInCents)。如果直接调用 SDK,它的接口就会泄漏到各处。下面的两个接口明显不兼容——适配器正是用来解决这种不匹配的。
<?php
// What our app wants to depend on
interface PaymentGateway { public function pay(float $dollars): string; }
// What the vendor actually gives us (cents, array result)
final class StripeSdk {
public function chargeCard(int $cents): array {
return ['id' => 'ch_1', 'amount' => $cents];
}
}
// dollars vs cents, string vs array -> shapes don't line up
var_dump((new StripeSdk())->chargeCard(2000));
代码中的适配器
适配器会持有被适配对象并实现您的目标接口,同时转换参数和返回值。这样,应用程序代码可以保持整洁,并且不依赖供应商。
<?php
interface PaymentGateway { public function pay(float $dollars): string; }
// Third-party SDK with an incompatible API
final class StripeSdk {
public function chargeCard(int $cents): array {
return ['id' => 'ch_1', 'amount' => $cents];
}
}
final class StripeAdapter implements PaymentGateway {
public function __construct(private StripeSdk $sdk) {}
public function pay(float $dollars): string {
$res = $this->sdk->chargeCard((int) round($dollars * 100));
return "charged {$res['amount']}c -> {$res['id']}";
}
}
echo (new StripeAdapter(new StripeSdk()))->pay(20.0), PHP_EOL;
装饰器:基本思路
装饰器包装一个与自身共享接口的对象,并在委托前后添加行为。与继承不同,装饰器可以在运行时叠加,让您能够以任意顺序组合功能,而不会导致类数量爆炸。下面是最小骨架:一个实现相同接口并持有内部实例的包装器。
<?php
interface Coffee { public function cost(): float; }
final class Espresso implements Coffee {
public function cost(): float { return 2.0; }
}
final class WithMilk implements Coffee {
public function __construct(private Coffee $inner) {}
public function cost(): float { return $this->inner->cost() + 0.5; }
}
echo (new WithMilk(new Espresso()))->cost(), PHP_EOL; // 2.5
代码中的装饰器
每个装饰器都实现相同的接口,并持有一个内部实例。这里,我们为数据源添加缓存和日志记录,并自由地组合这些功能。
<?php
interface DataSource { public function read(): string; }
final class FileSource implements DataSource {
public function read(): string { return 'raw-data'; }
}
final class UpperCaseDecorator implements DataSource {
public function __construct(private DataSource $inner) {}
public function read(): string { return strtoupper($this->inner->read()); }
}
final class ExclaimDecorator implements DataSource {
public function __construct(private DataSource $inner) {}
public function read(): string { return $this->inner->read() . '!!!'; }
}
$src = new ExclaimDecorator(new UpperCaseDecorator(new FileSource()));
echo $src->read(), PHP_EOL; // RAW-DATA!!!
实际应用中的装饰器
PSR-7 / PSR-15 中间件本质上是将装饰器模式应用于超文本传输协议处理:每个中间件都会包装下一个中间件,在请求和响应周围添加身份验证、日志记录或缓存。流包装器以及 PSR-6/16 缓存层也经常使用装饰。其关键特征是:包装器可以替代它所包装的对象。
装饰器与继承的比较
继承会在编译时固定行为,并且只允许一个基类。装饰则是动态且可组合的。以下情况适合选择装饰器:
- 可选功能的组合数量不断增加;
- 行为需要在运行时添加或移除;
- 必须扩展一个标记为
final或来自第三方且无法继承的类。
外观:基本思路
外观为由许多类组成的复杂子系统提供单一、简化的入口。它不会隐藏子系统(您仍然可以直接访问其中的部分),但会为常见场景提供便捷的高级应用程序接口。请将下面繁琐的子系统调用流程与下一场景中的单行外观调用进行比较。
<?php
// Without a facade, the client wires every step itself
final class Cpu { public function boot(): string { return 'cpu '; } }
final class Disk { public function load(): string { return 'disk '; } }
final class Ram { public function check(): string { return 'ram'; } }
$out = (new Cpu())->boot() . (new Disk())->load() . (new Ram())->check();
echo $out, PHP_EOL; // cpu disk ram
代码中的外观
外观会通过一个方法协调多个协作者。客户端调用place(),而不必自行连接库存、支付和配送。
<?php
final class Inventory { public function reserve(string $sku): bool { return true; } }
final class Payments { public function charge(float $amt): bool { return true; } }
final class Shipping { public function dispatch(string $sku): string { return 'tracking-99'; } }
final class OrderFacade {
public function __construct(
private Inventory $inv,
private Payments $pay,
private Shipping $ship
) {}
public function place(string $sku, float $amount): string {
if (!$this->inv->reserve($sku)) { return 'out of stock'; }
if (!$this->pay->charge($amount)) { return 'payment failed'; }
return 'shipped: ' . $this->ship->dispatch($sku);
}
}
echo (new OrderFacade(new Inventory(), new Payments(), new Shipping()))
->place('ABC', 49.99), PHP_EOL;
区分三者
- 适配器改变接口,让两个现有部分能够通信;行为相同,形式不同。
- 装饰器保留接口但添加行为,并且可以在运行时叠加。
- 外观在多个类的前面引入一个新的、更简单的接口。
一个实用的判断方法是:适配器关注兼容性,装饰器关注增强,外观关注简化。
关于拉拉维尔外观的说明
拉拉维尔的“外观”是一种由服务容器支持的静态代理机制,并不是四人组的外观模式。它们的目标都是提供便捷入口,但实现方式不同。在思考设计时,请不要把框架术语与结构型模式混为一谈。
快速检查
请根据模式的意图识别它。
回顾
您使用三种结构型模式组合了对象:使用适配器连接不兼容的接口(例如供应商 SDK),使用装饰器在运行时叠加行为(PSR-15 中间件的基础),以及使用外观为复杂子系统提供简单入口。每种模式都能降低耦合,让代码不易受供应商锁定和功能膨胀的影响。
常见问题解答
「结构型模式:适配器、装饰器、外观」课时是免费的吗?
是的 — 「结构型模式:适配器、装饰器、外观」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。
「结构型模式:适配器、装饰器、外观」这节课中我会学到什么?
使用结构型模式组合对象并简化接口。 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 PHP Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「结构型模式:适配器、装饰器、外观」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 PHP Academy 课中编写并运行代码吗?
能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 实践 SOLID 原则
- 创建型模式:工厂、构建器、单例
- 结构型模式:适配器、装饰器、外观
- 行为型模式:策略、观察者、命令