PHP外观模式简化调用

wen PHP项目 1

PHP 外观模式(Facade Pattern)— 简化复杂系统调用

什么是外观模式?

外观模式提供了一个统一的简化接口,用来访问子系统中的一群接口,它为子系统中的一组接口提供一个一致的界面,使得子系统更容易使用。

PHP外观模式简化调用

用通俗的话说:外观模式就是给复杂的系统提供一个"门面",让客户端只需要和门面打交道,而不需要了解系统内部复杂的交互逻辑。

核心结构

┌─────────────────────────────────────────┐
│            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>" .
               "&nbsp;&nbsp;\"status\": {$response['status']},<br>" .
               "&nbsp;&nbsp;\"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. 需要为一组复杂的子系统提供简化接口
  2. 客户端需要与多个子系统解耦
  3. 系统有层次结构,需要定义每一层的入口点
  4. 需要将系统划分为多个独立层次

最佳实践

// 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 设计系统分层等场景。

外观模式不是"万能药",当系统本身很简单时,使用外观模式反而会增加不必要的复杂度。适度使用才是关键。

抱歉,评论功能暂时关闭!