多态在依赖注入容器中的典型应用
解读
国内一线互联网公司的 PHP 面试,越来越把“依赖注入容器(DI Container)”当成区分初中高级的分水岭。
面试官抛出“多态在 DI 容器中的典型应用”,并不是想听你背“多态三要素”,而是想看:
- 你是否能把“接口-实现”这种多态思维落地到框架级代码;
- 你是否理解容器在解析依赖时如何利用多态完成解耦、替换、AOP 与测试替身;
- 你是否能结合 Composer 自动加载、PSR-11 容器接口、Laravel/Symfony 源码讲出“中国程序员日常是怎么用的”。
一句话:用中文把“多态”讲成“能上线的代码”,而不是“课本里的定义”。
知识点
- 多态(Polymorphism):同一接口,不同实现;运行时绑定。
- 依赖注入容器(DI Container):负责“new 对象”与“装配对象”的工厂,核心能力是“自动解析依赖”与“管理生命周期”。
- 绑定(bind)与解析(resolve):容器里把抽象(接口/抽象类)映射到具体实现,解析时根据绑定关系生成实例。
- 上下文绑定(Contextual Binding):同一接口在不同消费类中注入不同实现,是多态在容器里最直观的落地。
- 替换与mock:测试环境把真实实现换成内存实现,靠多态+容器一键切换,不写一行 if。
- 标签与装饰器:利用多态把一组实现打上标签,循环注入;或者采用装饰器模式层层包装,容器自动洋葱式包裹。
- PSR-11:国内主流框架都实现了该规范,面试时提到“符合 PSR-11”是加分项。
答案
我以 Laravel 8+ 为例,给出一段可直接跑进 Composer 项目的代码,展示“多态在 DI 容器中的三种典型应用”。
目录结构(PSR-4): app/ ├── Services/ │ ├── Payment/ │ │ ├── PaymentGateway.php // 接口 │ │ ├── AlipayGateway.php // 实现A │ │ └── WechatGateway.php // 实现B │ └── OrderService.php // 消费方 ├── Providers/ │ └── PaymentServiceProvider.php // 绑定关系 tests/ ├── Unit/ │ └── OrderServiceTest.php // 单元测试
- 定义接口——多态的“契约”
namespace App\Services\Payment;
interface PaymentGateway
{
public function pay(array $order): string; // 返回支付链接
}
- 多态实现——同一接口,不同支付渠道
namespace App\Services\Payment;
class AlipayGateway implements PaymentGateway
{
public function pay(array $order): string
{
// 调用支付宝 SDK,返回跳转链接
return 'https://openapi.alipay.com/…';
}
}
class WechatGateway implements PaymentGateway
{
public function pay(array $order): string
{
// 调用微信 SDK,返回二维码链接
return 'weixin://wxpay/…';
}
}
- 消费方——通过构造函数注入“接口”,运行时由容器给具体实现
namespace App\Services;
use App\Services\Payment\PaymentGateway;
class OrderService
{
public function __construct(
private PaymentGateway $gateway
) {}
public function createOrder(array $cart): array
{
$url = $this->gateway->pay($cart);
return ['pay_url' => $url];
}
}
- 在 ServiceProvider 里做“多态绑定”——国内项目最常见的三种场景
场景1:全局默认实现(生产环境走支付宝)
public function register(): void
{
$this->app->bind(
\App\Services\Payment\PaymentGateway::class,
\App\Services\Payment\AlipayGateway::class
);
}
场景2:上下文绑定(后台订单走支付宝,小程序订单走微信)
public function register(): void
{
$this->app->when(\App\Services\OrderService::class)
->needs(\App\Services\Payment\PaymentGateway::class)
->give(function ($app) {
// 根据当前请求域名或 header 动态给实现
return request()->header('x-channel') === 'wechat'
? new \App\Services\Payment\WechatGateway
: new \App\Services\Payment\AlipayGateway;
});
}
场景3:测试环境一键替换(单元测试用内存实现)
// tests/Unit/OrderServiceTest.php
public function test_create_order()
{
// 容器层面替换,无需改业务代码
$this->app->bind(
\App\Services\Payment\PaymentGateway::class,
\App\Services\Payment\FakeGateway::class // 返回 fake 链接
);
$service = app(\App\Services\OrderService::class);
$res = $service->createOrder(['id' => 1]);
$this->assertEquals('fake://pay', $res['pay_url']);
}
运行结果:
- 生产环境
php artisan serve默认走支付宝; - 带
x-channel: wechat头走微信; - 单元测试走内存实现,0 外部依赖,CI 秒过。
整段代码没有 new、没有 if/else、没有工厂类,却完成了“渠道切换”“测试替身”“上下文路由”,这就是多态在 DI 容器中的典型应用。
拓展思考
- 性能陷阱:上下文绑定每次请求都会回调,高并发场景可改用“单例绑定”+“策略模式”缓存实例。
- 装饰器链:国内电商大促时常用“日志装饰器→风控装饰器→真正支付”,容器支持
extend()方法自动洋葱式包装,面试可提“比中间件更轻量”。 - 兼容 Symfony:Symfony 的
DecoratorInterface与Alias也能完成同样效果,可对比Laravel的Contextual Binding在可读性上的优劣。 - 面向包开发:在 Composer 包里只提供接口,把实现类放到下游业务层,通过容器合并配置,实现“插件化”市场,国内 SaaS 厂商常用此套路卖“增值支付插件”。
- 面试反问:可以问面试官“贵司容器是否自研?是否支持 PSR-11?线上灰度时如何做实现类切换?”体现你对国内运维灰度体系的了解。