| 維度 | 依賴注入 (DI) | 控制反轉 (IoC) |
|---|---|---|
| 視角 | 應用程序視角 | 容器視角 |
| 描述 | 應用程序依賴容器創建並注入所需外部資源 | 容器控制應用程序,反向向應用注入資源 |
| 關鍵點 | 「注入」動作 | 「控制權」轉移 |
本質上是同一體系的兩種描述方式:IoC 是設計原則,DI 是實現模式。
OrderService 需要 PaymentGateway)。// 傳統強耦合寫法
class OrderService {
private $payment;
public function __construct() {
$this->payment = new AlipayGateway(); // 死綁定具體實現
}
}
痛點:難以測試(無法 Mock)、難以擴展(換支付需改代碼)、違反開閉原則。
class OrderService {
private $payment;
public function __construct(PaymentGatewayInterface $payment) {
$this->payment = $payment;
}
}
// 容器自動解析構造函數參數類型提示,注入綁定的實現
優點:依賴不可變、必須依賴、易於單元測試。
class OrderService {
private $payment;
public function setPayment(PaymentGatewayInterface $payment) {
$this->payment = $payment;
}
}
// 適用於可選依賴、運行時動態切換
類實現特定接口(如 InjectableInterface),容器調用接口方法注入。較少用,耦合度較高。
class Container {
private $bindings = [];
private $instances = [];
// 綁定接口到實現(支持閉包延遲實例化)
public function bind(string $abstract, $concrete = null, bool $shared = false) {
$this->bindings[$abstract] = ['concrete' => $concrete ?? $abstract, 'shared' => $shared];
}
// 別名綁定
public function alias(string $abstract, string $alias) {
$this->bind($alias, $abstract);
}
// 解析實例
public function make(string $abstract, array $parameters = []) {
if (isset($this->instances[$abstract])) return $this->instances[$abstract];
$binding = $this->bindings[$abstract] ?? ['concrete' => $abstract, 'shared' => false];
$concrete = $binding['concrete'];
// 閉包工廠
if ($concrete instanceof Closure) {
$instance = $concrete($this, $parameters);
} else {
$instance = $this->build($concrete, $parameters);
}
if ($binding['shared']) $this->instances[$abstract] = $instance;
return $instance;
}
private function build(string $concrete, array $parameters) {
$reflector = new ReflectionClass($concrete);
if (!$reflector->isInstantiable()) throw new Exception("Class $concrete not instantiable");
$constructor = $reflector->getConstructor();
if (!$constructor) return new $concrete;
$dependencies = $this->resolveDependencies($constructor->getParameters(), $parameters);
return $reflector->newInstanceArgs($dependencies);
}
private function resolveDependencies(array $params, array $given) {
return array_map(function($param) use ($given) {
if (isset($given[$param->name])) return $given[$param->name];
if ($param->getClass()) return $this->make($param->getClass()->name);
if ($param->isDefaultValueAvailable()) return $param->getDefaultValue();
throw new Exception("Unresolvable dependency: {$param->name}");
}, $params);
}
}
// 使用示例
$container = new Container();
$container->bind(PaymentGatewayInterface::class, AlipayGateway::class, true); // 單例
$container->bind(OrderService::class); // 自動解析構造函數
$orderService = $container->make(OrderService::class);
現代框架(Laravel、Symfony、Yii、ThinkPHP)均實現 Psr\Container\ContainerInterface:
interface ContainerInterface {
public function get(string $id);
public function has(string $id): bool;
}
遵循標準意味著組件可在不同框架間復用,生態互通。
Interface,綁定在啟動階段集中配置。$container->make(),這會隱藏依賴關係。DI/IoC 不是為了「高大上」而引入,而是為了解決耦合與可測試性這兩個軟件工程核心問題。掌握容器原理,能讓你在任何框架中游刃有餘地管理依賴關係。