PHP 外观模式(Facade Pattern)— 简化复杂系统调用
什么是外观模式?
外观模式提供了一个统一的简化接口,用来访问子系统中的一群接口,它为子系统中的一组接口提供一个一致的界面,使得子系统更容易使用。

用通俗的话说:外观模式就是给复杂的系统提供一个"门面",让客户端只需要和门面打交道,而不需要了解系统内部复杂的交互逻辑。
核心结构
┌─────────────────────────────────────────┐
│ Client(客户端) │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Facade(外观类) │
│ - 知道哪些子系统负责处理请求 │
│ - 将客户端请求代理给适当的子系统对象 │
└──────────────────┬──────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 子系统A │ │ 子系统B │ │ 子系统C │
└─────────┘ └─────────┘ └─────────┘
代码示例
场景:家庭影院系统
<?php
// ==================== 子系统类 ====================
class Amplifier {
public function on() {
echo "功放:已开启<br>";
}
public function setVolume($level) {
echo "功放:音量设置为 {$level}<br>";
}
public function off() {
echo "功放:已关闭<br>";
}
}
class DVDPlayer {
public function on() {
echo "DVD播放器:已开启<br>";
}
public function play($movie) {
echo "DVD播放器:正在播放《{$movie}》<br>";
}
public function off() {
echo "DVD播放器:已关闭<br>";
}
}
class Projector {
public function on() {
echo "投影仪:已开启<br>";
}
public function setInput($source) {
echo "投影仪:输入源切换到 {$source}<br>";
}
public function off() {
echo "投影仪:已关闭<br>";
}
}
class Screen {
public function down() {
echo "屏幕:已放下<br>";
}
public function up() {
echo "屏幕:已收起<br>";
}
}
class Lights {
public function dim($level) {
echo "灯光:调暗至 {$level}%<br>";
}
public function on() {
echo "灯光:已全亮<br>";
}
}
// ==================== 外观类 ====================
class HomeTheaterFacade {
private $amplifier;
private $dvdPlayer;
private $projector;
private $screen;
private $lights;
public function __construct(
Amplifier $amplifier,
DVDPlayer $dvdPlayer,
Projector $projector,
Screen $screen,
Lights $lights
) {
$this->amplifier = $amplifier;
$this->dvdPlayer = $dvdPlayer;
$this->projector = $projector;
$this->screen = $screen;
$this->lights = $lights;
}
// 简化看电影的完整流程
public function watchMovie($movie) {
echo "===== 开始观影:{$movie} =====<br>";
// 内部处理各子系统的复杂交互
$this->lights->dim(10); // 调暗灯光
$this->screen->down(); // 放下屏幕
$this->projector->on(); // 开启投影仪
$this->projector->setInput('DVD'); // 切换输入源
$this->amplifier->on(); // 开启功放
$this->amplifier->setVolume(20); // 设置音量
$this->dvdPlayer->on(); // 开启播放器
$this->dvdPlayer->play($movie); // 播放电影
echo "===== 观影准备完成 =====<br><br>";
}
// 简化结束观影的流程
public function endMovie() {
echo "===== 结束观影 =====<br>";
$this->dvdPlayer->off(); // 关闭播放器
$this->amplifier->off(); // 关闭功放
$this->projector->off(); // 关闭投影仪
$this->screen->up(); // 收起屏幕
$this->lights->on(); // 打开灯光
echo "===== 观影结束,恢复原状 =====<br><br>";
}
}
// ==================== 客户端使用 ====================
// 不使用外观模式时,客户端需要知道所有细节
function withoutFacade($movie) {
echo "=== 不使用外观模式的观影流程 ===<br>";
$amplifier = new Amplifier();
$dvdPlayer = new DVDPlayer();
$projector = new Projector();
$screen = new Screen();
$lights = new Lights();
// 客户端必须了解所有子系统的交互细节
$lights->dim(10);
$screen->down();
$projector->on();
$projector->setInput('DVD');
$amplifier->on();
$amplifier->setVolume(20);
$dvdPlayer->on();
$dvdPlayer->play($movie);
// 结束也需要逐个关闭
$dvdPlayer->off();
$amplifier->off();
$projector->off();
$screen->up();
$lights->on();
echo "<br>";
}
// 使用外观模式
function withFacade($movie) {
echo "=== 使用外观模式 ===<br>";
$homeTheater = new HomeTheaterFacade(
new Amplifier(),
new DVDPlayer(),
new Projector(),
new Screen(),
new Lights()
);
// 只需调用一个方法即可
$homeTheater->watchMovie($movie);
$homeTheater->endMovie();
}
// 测试
withoutFacade("星际穿越");
withFacade("星际穿越");
输出结果:
=== 不使用外观模式的观影流程 ===
灯光:调暗至 10%
屏幕:已放下
投影仪:已开启
投影仪:输入源切换到 DVD
功放:已开启
功放:音量设置为 20
DVD播放器:已开启
DVD播放器:正在播放《星际穿越》
DVD播放器:已关闭
功放:已关闭
投影仪:已关闭
屏幕:已收起
灯光:已全亮
=== 使用外观模式 ===
===== 开始观影:星际穿越 =====
灯光:调暗至 10%
屏幕:已放下
投影仪:已开启
投影仪:输入源切换到 DVD
功放:已开启
功放:音量设置为 20
DVD播放器:已开启
DVD播放器:正在播放《星际穿越》
===== 观影准备完成 =====
===== 结束观影 =====
DVD播放器:已关闭
功放:已关闭
投影仪:已关闭
屏幕:已收起
灯光:已全亮
===== 观影结束,恢复原状 =====
进阶示例:API 客户端
<?php
// ==================== 复杂子系统 ====================
class HttpRequest {
public function send($url, $method, $data = []) {
// 模拟 HTTP 请求
echo "发送 {$method} 请求到 {$url}<br>";
return [
'status' => 200,
'data' => ['id' => 1, 'name' => '张三']
];
}
}
class DataValidator {
public function validate($data) {
if (empty($data['name'])) {
throw new Exception("名称不能为空");
}
return true;
}
}
class ResponseFormatter {
public function format($response) {
// 模拟格式化响应
return "响应内容:{<br>" .
" \"status\": {$response['status']},<br>" .
" \"data\": " . json_encode($response['data']) . "<br>" .
"}";
}
}
class Logger {
public function log($message) {
echo "[日志] {$message}<br>";
}
}
// ==================== 外观类 ====================
class ApiClientFacade {
private $httpRequest;
private $validator;
private $formatter;
private $logger;
public function __construct() {
$this->httpRequest = new HttpRequest();
$this->validator = new DataValidator();
$this->formatter = new ResponseFormatter();
$this->logger = new Logger();
}
// 统一的 GET 请求接口
public function get($url, $params = []) {
$this->logger->log("开始 GET 请求");
$response = $this->httpRequest->send($url, 'GET', $params);
return $this->formatter->format($response);
}
// 统一的 POST 请求接口
public function post($url, $data) {
try {
$this->logger->log("开始 POST 请求");
// 内部自动完成验证
$this->validator->validate($data);
$response = $this->httpRequest->send($url, 'POST', $data);
return $this->formatter->format($response);
} catch (Exception $e) {
$this->logger->log("POST 请求失败: " . $e->getMessage());
throw $e;
}
}
// 统一的 PUT 请求接口
public function put($url, $data) {
$this->logger->log("开始 PUT 请求");
$response = $this->httpRequest->send($url, 'PUT', $data);
return $this->formatter->format($response);
}
// 统一的 DELETE 请求接口
public function delete($url, $id) {
$this->logger->log("开始 DELETE 请求");
$response = $this->httpRequest->send($url, 'DELETE', ['id' => $id]);
return $this->formatter->format($response);
}
}
// ==================== 客户端使用 ====================
$apiClient = new ApiClientFacade();
// 客户端只需要调用简单的接口
try {
// GET 请求
echo "<strong>GET 请求:</strong><br>";
$result = $apiClient->get('/api/users/1');
echo $result . "<br><br>";
// POST 请求
echo "<strong>POST 请求:</strong><br>";
$result = $apiClient->post('/api/users', ['name' => '李四']);
echo $result . "<br><br>";
// POST 请求(验证失败)
echo "<strong>POST 请求(数据无效):</strong><br>";
$apiClient->post('/api/users', []);
} catch (Exception $e) {
echo "错误:" . $e->getMessage() . "<br>";
}
外观模式的优缺点
优点 ✅
| 优点 | 说明 |
|---|---|
| 简化接口 | 隐藏系统的复杂性,提供统一简洁的接口 |
| 解耦合 | 客户端与子系统解耦,降低耦合度 |
| 提高安全性和灵活性 | 只暴露必要的方法,内部可以自由修改 |
| 易于使用 | 降低学习成本,客户端不需要了解内部细节 |
| 便于分层 | 有助于建立层次结构,各层通过外观通信 |
缺点 ❌
| 缺点 | 说明 |
|---|---|
| 可能成为"上帝对象" | 如果不加控制,外观类可能变得过于庞大 |
| 不一定符合开闭原则 | 修改外观接口可能需要修改客户端 |
| 过度使用会隐藏问题 | 可能掩盖内部组件的真实问题 |
适用场景与最佳实践
适用场景
- 需要为一组复杂的子系统提供简化接口
- 客户端需要与多个子系统解耦
- 系统有层次结构,需要定义每一层的入口点
- 需要将系统划分为多个独立层次
最佳实践
// 1. 组合优于继承:外观类应该使用组合而不是继承
class Facade {
private $subSystemA;
private $subSystemB;
public function __construct(SubSystemA $a, SubSystemB $b) {
$this->subSystemA = $a;
$this->subSystemB = $b;
}
}
// 2. 外观接口应该保持简单,专注于客户端需求
class OrderFacade {
public function __construct(private InventorySystem $inventory,
private PaymentSystem $payment) {}
public function checkout($orderId) {
// 简单的业务逻辑组合
}
}
// 3. 不要在外观类中添加业务逻辑,只做调度
class BookingFacade {
public function bookFlight($flightId) {
// 只做调度,不做具体业务
return $this->flightSystem->book($flightId);
}
}
对比其他模式
| 比较 | 外观模式 | 适配器模式 | 代理模式 |
|---|---|---|---|
| 目的 | 简化接口 | 转换接口 | 控制访问 |
| 焦点 | 简化复杂系统 | 适配不同接口 | 延迟加载/权限控制 |
| 结构 | 多个子系统 | 两个不同接口 | 被代理对象 |
外观模式的核心价值:通过提供一个统一的入口,将复杂的系统内部交互对外隐藏,使得客户端代码更加简洁、清晰、易于维护,它特别适合用于库的封装、API 设计和系统分层等场景。
外观模式不是"万能药",当系统本身很简单时,使用外观模式反而会增加不必要的复杂度。适度使用才是关键。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播