多态在依赖注入容器中的典型应用

解读

国内一线互联网公司的 PHP 面试,越来越把“依赖注入容器(DI Container)”当成区分初中高级的分水岭。
面试官抛出“多态在 DI 容器中的典型应用”,并不是想听你背“多态三要素”,而是想看:

  1. 你是否能把“接口-实现”这种多态思维落地到框架级代码;
  2. 你是否理解容器在解析依赖时如何利用多态完成解耦、替换、AOP 与测试替身;
  3. 你是否能结合 Composer 自动加载、PSR-11 容器接口、Laravel/Symfony 源码讲出“中国程序员日常是怎么用的”。
    一句话:用中文把“多态”讲成“能上线的代码”,而不是“课本里的定义”。

知识点

  1. 多态(Polymorphism):同一接口,不同实现;运行时绑定。
  2. 依赖注入容器(DI Container):负责“new 对象”与“装配对象”的工厂,核心能力是“自动解析依赖”与“管理生命周期”。
  3. 绑定(bind)与解析(resolve):容器里把抽象(接口/抽象类)映射到具体实现,解析时根据绑定关系生成实例。
  4. 上下文绑定(Contextual Binding):同一接口在不同消费类中注入不同实现,是多态在容器里最直观的落地。
  5. 替换与mock:测试环境把真实实现换成内存实现,靠多态+容器一键切换,不写一行 if。
  6. 标签与装饰器:利用多态把一组实现打上标签,循环注入;或者采用装饰器模式层层包装,容器自动洋葱式包裹。
  7. 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 // 单元测试

  1. 定义接口——多态的“契约”
namespace App\Services\Payment;

interface PaymentGateway
{
    public function pay(array $order): string; // 返回支付链接
}
  1. 多态实现——同一接口,不同支付渠道
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/…';
    }
}
  1. 消费方——通过构造函数注入“接口”,运行时由容器给具体实现
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];
    }
}
  1. 在 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 容器中的典型应用。

拓展思考

  1. 性能陷阱:上下文绑定每次请求都会回调,高并发场景可改用“单例绑定”+“策略模式”缓存实例。
  2. 装饰器链:国内电商大促时常用“日志装饰器→风控装饰器→真正支付”,容器支持 extend() 方法自动洋葱式包装,面试可提“比中间件更轻量”。
  3. 兼容 Symfony:Symfony 的 DecoratorInterfaceAlias 也能完成同样效果,可对比 LaravelContextual Binding 在可读性上的优劣。
  4. 面向包开发:在 Composer 包里只提供接口,把实现类放到下游业务层,通过容器合并配置,实现“插件化”市场,国内 SaaS 厂商常用此套路卖“增值支付插件”。
  5. 面试反问:可以问面试官“贵司容器是否自研?是否支持 PSR-11?线上灰度时如何做实现类切换?”体现你对国内运维灰度体系的了解。