Skip to content

代理模式(Proxy)

前言

代理模式为其他对象提供一种代理以控制对这个对象的访问。它在不改变原始对象接口的前提下,通过代理对象增强或控制对原对象的访问。本文将详细讲解代理模式的核心原理、多种代理类型及实际应用。


一、核心概念

1.1 定义

为其他对象提供一种代理以控制对这个对象的访问。

1.2 核心角色

角色说明
主题接口(Subject)定义真实主题和代理的共同接口
真实主题(RealSubject)实际执行业务逻辑的对象
代理(Proxy)控制对真实主题的访问

1.3 代理类型

类型说明场景
远程代理为远程对象提供本地代表RPC 调用
虚拟代理延迟创建开销大的对象图片懒加载
保护代理控制访问权限权限校验
智能引用在访问时附加操作引用计数、日志
缓存代理缓存结果API 请求缓存

二、代码实现

2.1 虚拟代理(延迟加载)

php
<?php
// 主题接口
interface Image
{
    public function display(): void;
}

// 真实主题:大图片加载
class RealImage implements Image
{
    private string $filename;

    public function __construct(string $filename)
    {
        $this->filename = $filename;
        $this->loadFromDisk();
    }

    private function loadFromDisk(): void
    {
        echo "Loading {$this->filename} from disk..." . PHP_EOL;
    }

    public function display(): void
    {
        echo "Displaying {$this->filename}" . PHP_EOL;
    }
}

// 虚拟代理:延迟加载图片
class ProxyImage implements Image
{
    private ?RealImage $realImage = null;
    private string $filename;

    public function __construct(string $filename)
    {
        $this->filename = $filename;
    }

    public function display(): void
    {
        // 只有真正需要显示时才加载
        if ($this->realImage === null) {
            $this->realImage = new RealImage($this->filename);
        }
        $this->realImage->display();
    }
}

// 使用
$image = new ProxyImage("photo.jpg");
echo "Image created but not loaded yet" . PHP_EOL;
$image->display(); // 此时才加载
$image->display(); // 复用已加载实例

2.2 保护代理(权限控制)

php
<?php
interface Document
{
    public function read(): string;
    public function write(string $content): void;
}

class RealDocument implements Document
{
    private string $content;

    public function __construct(string $content)
    {
        $this->content = $content;
    }

    public function read(): string
    {
        return $this->content;
    }

    public function write(string $content): void
    {
        $this->content = $content;
    }
}

class ProtectedDocument implements Document
{
    private RealDocument $realDocument;
    private string $userRole;

    public function __construct(RealDocument $realDocument, string $userRole)
    {
        $this->realDocument = $realDocument;
        $this->userRole = $userRole;
    }

    public function read(): string
    {
        echo "[Audit] {$this->userRole} read document" . PHP_EOL;
        return $this->realDocument->read();
    }

    public function write(string $content): void
    {
        if (!in_array($this->userRole, ['admin', 'editor'], true)) {
            throw new RuntimeException("Permission denied: write requires admin or editor role");
        }
        echo "[Audit] {$this->userRole} wrote to document" . PHP_EOL;
        $this->realDocument->write($content);
    }
}

// 使用
$doc = new ProtectedDocument(new RealDocument("Hello"), "viewer");
echo $doc->read() . PHP_EOL; // 允许读取
$doc->write("World"); // 报错:Permission denied

2.3 缓存代理

php
<?php
interface DataService
{
    public function getData(string $key): string;
}

class RemoteDataService implements DataService
{
    public function getData(string $key): string
    {
        echo "Fetching {$key} from remote server..." . PHP_EOL;
        return "Data for {$key}";
    }
}

class CachingProxy implements DataService
{
    private RemoteDataService $realService;
    private array $cache = [];
    private int $ttl;

    public function __construct(RemoteDataService $realService, int $ttl = 60)
    {
        $this->realService = $realService;
        $this->ttl = $ttl;
    }

    public function getData(string $key): string
    {
        $now = time();

        if (isset($this->cache[$key]) && $this->cache[$key]['expiry'] > $now) {
            echo "Cache hit for {$key}" . PHP_EOL;
            return $this->cache[$key]['value'];
        }

        $value = $this->realService->getData($key);
        $this->cache[$key] = ['value' => $value, 'expiry' => $now + $this->ttl];
        return $value;
    }
}

// 使用
$service = new CachingProxy(new RemoteDataService());
$service->getData("user:1"); // 从远程获取
$service->getData("user:1"); // 从缓存获取

2.4 智能引用代理

php
<?php
class SmartReferenceProxy implements Image
{
    private ?RealImage $realImage = null;
    private string $filename;
    private int $accessCount = 0;

    public function __construct(string $filename)
    {
        $this->filename = $filename;
    }

    public function display(): void
    {
        $this->accessCount++;
        echo "[Access count: {$this->accessCount}]" . PHP_EOL;

        if ($this->realImage === null) {
            $this->realImage = new RealImage($this->filename);
        }
        $this->realImage->display();
    }
}

三、适用场景

场景代理类型说明
图片懒加载虚拟代理延迟加载大资源
权限校验保护代理控制访问权限
API 缓存缓存代理减少网络请求
日志记录智能引用记录访问行为
远程调用远程代理封装网络通信
资源管理智能引用引用计数

四、优缺点分析

优点缺点
控制对象访问增加代理层
延迟加载节省资源可能影响性能
权限控制代码复杂度增加
附加功能(日志/缓存)代理类需要维护
符合开闭原则响应可能延迟

五、常见踩坑与问题排查

5.1 代理与装饰器混淆

php
<?php
// 装饰器:增强功能,关注"添加什么"
// 代理:控制访问,关注"是否允许"
// 区别:代理通常由框架控制,装饰器由客户端组合

5.2 代理层过多影响性能

php
<?php
// 问题:多层代理叠加
$service = new LoggingProxy(
    new CachingProxy(new ProtectedProxy(new RemoteDataService()))
);

// 解决:合理规划代理层级,合并部分功能

六、优化方案与进阶

6.1 动态代理(基于反射)

php
<?php
// 通过反射动态代理对象方法
class DynamicProxy
{
    private object $target;
    private array $interceptors = [];

    public function __construct(object $target)
    {
        $this->target = $target;
    }

    public function addInterceptor(string $method, callable $fn): void
    {
        $this->interceptors[$method] = $fn;
    }

    public function __call(string $name, array $arguments): mixed
    {
        if (isset($this->interceptors[$name])) {
            echo "[Access] Calling {$name}" . PHP_EOL;
        }
        return call_user_func_array([$this->target, $name], $arguments);
    }
}

class UserService
{
    public function getUser(int $id): array
    {
        return ['id' => $id, 'name' => 'Flynn'];
    }
}

$proxy = new DynamicProxy(new UserService());
$proxy->addInterceptor('getUser', fn() => null);
$user = $proxy->getUser(1);
// [Access] Calling getUser

6.2 拦截器模式

php
<?php
class ApiClient
{
    private string $baseURL;

    public function __construct(string $baseURL)
    {
        $this->baseURL = $baseURL;
    }

    // 代理 cURL 请求
    public function request(string $url, array $options = []): string
    {
        $fullUrl = "{$this->baseURL}{$url}";

        $ch = curl_init($fullUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        // 前置拦截:添加 token
        $token = $_SESSION['token'] ?? null;
        if ($token) {
            $headers = $options['headers'] ?? [];
            $headers[] = "Authorization: Bearer {$token}";
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        }

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        // 后置拦截:处理 401
        if ($httpCode === 401) {
            unset($_SESSION['token']);
            header('Location: /login');
            exit;
        }

        return $response;
    }
}

七、全文总结

代理模式的核心是 通过代理对象控制对原对象的访问,提供额外功能

核心要点

  1. 代理和真实对象实现相同接口
  2. 五种代理类型:远程、虚拟、保护、智能引用、缓存
  3. PHP 可通过 __call 魔术方法实现动态代理
  4. 适用于懒加载、权限控制、缓存、日志等场景
  5. 避免代理层过多,合理规划功能分层