本文目录导读:

在 PHP 中,“委派”(Delegation)是一种设计模式,核心思想是一个对象将某些任务交给另一个对象去完成,而不是自己亲自实现,这可以降低耦合度、提高代码复用性,并让职责更加清晰。
PHP 本身没有像 Java 那样专门的 delegate 关键字,但可以通过以下几种方式实现委派模式。
显式委派(最常用)
一个对象持有另一个对象的引用,并在自己的方法中调用该对象的方法。
<?php
class Printer
{
public function print(string $message): void
{
echo "[Printer]: $message\n";
}
}
class Report
{
private Printer $printer;
public function __construct(Printer $printer)
{
$this->printer = $printer;
}
public function printReport(string $content): void
{
// 委派给 Printer 对象
$this->printer->print($content);
}
}
// 使用
$printer = new Printer();
$report = new Report($printer);
$report->printReport("2024年度财务报告"); // 输出: [Printer]: 2024年度财务报告
优点:类型安全、IDE 智能提示好、代码清晰。
缺点:需要手动添加委派方法,如果委派的方法很多,代码会冗余。
通过魔术方法 __call 实现动态委派
适用于对象需要将多个方法的调用全部转发给另一个对象。
<?php
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
public function multiply(int $a, int $b): int
{
return $a * $b;
}
}
class CalculatorProxy
{
private Calculator $calculator;
public function __construct(Calculator $calculator)
{
$this->calculator = $calculator;
}
// 所有不存在的方法都会走这里
public function __call(string $name, array $arguments): mixed
{
if (method_exists($this->calculator, $name)) {
return $this->calculator->$name(...$arguments);
}
throw new \BadMethodCallException("Method $name not found.");
}
}
// 使用
$proxy = new CalculatorProxy(new Calculator());
echo $proxy->add(2, 3); // 5
echo $proxy->multiply(2, 3); // 6
优点:无需逐个写委派方法。
缺点:动态调用导致 IDE 无法静态分析、类型推断困难,性能稍低。
匿名函数 / Closure 委派
将可调用对象(闭包、函数)作为“委派目标”传递给另一个对象。
<?php
class TaskRunner
{
private $delegate;
// 接收任何可调用对象
public function setDelegate(callable $delegate): void
{
$this->delegate = $delegate;
}
public function run(string $task): void
{
if ($this->delegate) {
($this->delegate)($task); // 执行委派
}
}
}
// 使用
$runner = new TaskRunner();
$runner->setDelegate(function (string $task) {
echo "正在处理: $task\n";
});
$runner->run("备份数据库"); // 输出: 正在处理: 备份数据库
优点:非常灵活,无需定义新类。
缺点:难以进行类型约束,可读性依赖注释。
接口委派(组合优先于继承)
通过接口定义行为,将实现委派给具体的子类对象。
<?php
interface Logger
{
public function log(string $message): void;
}
class FileLogger implements Logger
{
public function log(string $message): void
{
file_put_contents('/tmp/log.txt', $message . PHP_EOL, FILE_APPEND);
}
}
class UserService
{
private Logger $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function register(string $email): void
{
// ... 注册逻辑 ...
$this->logger->log("用户 $email 注册成功");
}
}
// 使用
$service = new UserService(new FileLogger());
$service->register("test@example.com");
优点:符合 SOLID 原则,可以轻松更换不同的委派实现(如 FileLogger -> DatabaseLogger)。
缺点:需要定义接口和具体实现类,代码量稍多。
PHP 8 的构造函数属性提升 + 委托
PHP 8 可以更简洁地写出显式委派:
<?php
class NotificationService
{
public function __construct(
private EmailSender $emailSender,
private SmsSender $smsSender
) {}
public function sendWelcomeEmail(string $email): void
{
$this->emailSender->send($email, "欢迎加入!");
}
public function sendAlertSms(string $phone): void
{
$this->smsSender->send($phone, "系统告警!");
}
}
总结对比
| 方式 | 适用场景 | 类型安全 | 动态性 | 代码量 |
|---|---|---|---|---|
| 显式委派 | 固定、少量方法 | ✅ 高 | 低 | 中等 |
__call 魔术方法 |
大量方法需全部转发 | ❌ 低 | 高 | 较少 |
| 匿名函数/Closure | 临时逻辑、策略模式变体 | 中 | 高 | 少 |
| 接口委派 | 职责分离、可替换实现 | ✅ 高 | 低 | 较多 |
实际开发建议
- 优先使用显式委派或接口委派,代码可读性最好,IDE 支持最佳。
- 除非是框架层面的动态代理(如 Laravel 的 Facade、魔术方法),否则谨慎使用
__call。 - 委派模式在 策略模式、代理模式、桥接模式 中非常常见,理解委派有助于掌握这些模式。
如果你有具体的使用场景(比如想实现类似装饰器、动态代理等功能),可以告诉我,我可以给出更针对性的代码示例。