面向介面編程是「依賴倒置原則」的核心體現:高層模塊不應依賴低層模塊,二者都應依賴其抽象。本文通過一個計算器案例,展示 PHP 介面如何實現運算邏輯的多態切換、耦合解耦與測試友好。
<?php
interface Calculator {
public function calculate(float $a, float $b): float;
}
?>
<?php
class Addition implements Calculator {
public function calculate(float $a, float $b): float {
return $a + $b;
}
}
class Subtraction implements Calculator {
public function calculate(float $a, float $b): float {
return $a - $b;
}
}
class Multiplication implements Calculator {
public function calculate(float $a, float $b): float {
return $a * $b;
}
}
class Division implements Calculator {
public function calculate(float $a, float $b): float {
if ($b == 0) throw new DivisionByZeroError("除數不能為零");
return $a / $b;
}
}
class Modulo implements Calculator {
public function calculate(float $a, float $b): float {
return $a % $b;
}
}
?>
場景:引入一個「外星計算器」,其介面為 compute($x, $y),不符合我們的 Calculator 介面。
<?php
// 第三方類(無法修改)
class AlienCalculator {
public function compute(float $x, float $y): float {
return sqrt($x * $x + $y * $y); // 異星算法:勾股定理
}
}
// 適配器:實現我們的介面,委派給第三方
class AlienCalculatorAdapter implements Calculator {
public function __construct(private AlienCalculator $alien) {}
public function calculate(float $a, float $b): float {
return $this->alien->compute($a, $b);
}
}
?>
<?php
class CalculatorTester {
private float $operand1;
private float $operand2;
public function __construct(float $a, float $b) {
$this->operand1 = $a;
$this->operand2 = $b;
echo "計算器測試器初始化
";
echo "操作數: {$this->operand1}, {$this->operand2}
";
}
// 關鍵:參數類型聲明為介面
public function test(Calculator $calculator): string {
$result = $calculator->calculate($this->operand1, $this->operand2);
$className = get_class($calculator);
return "[$className] 計算結果: $result
";
}
}
?>
<?php
require_once 'Calculator.php';
require_once 'Operations.php';
require_once 'Adapter.php';
require_once 'CalculatorTester.php';
$tester = new CalculatorTester(10, 3);
// 1. 測試加法
$tester->test(new Addition());
// 2. 測試減法
$tester->test(new Subtraction());
// 3. 測試適配器(外星計算器)
$tester->test(new AlienCalculatorAdapter(new AlienCalculator()));
// 4. 運行時根據配置選擇
$operations = [
'add' => new Addition(),
'sub' => new Subtraction(),
'mul' => new Multiplication(),
'div' => new Division(),
'mod' => new Modulo(),
'alien' => new AlienCalculatorAdapter(new AlienCalculator()),
];
$selected = $_GET['op'] ?? 'add';
if (isset($operations[$selected])) {
echo $tester->test($operations[$selected]);
} else {
echo "未知操作: $selected";
}
?>
<?php
// 簡易容器
class Container {
private array $bindings = [];
private array $instances = [];
public function bind(string $abstract, callable $concrete): void {
$this->bindings[$abstract] = $concrete;
}
public function singleton(string $abstract, callable $concrete): void {
$this->bind($abstract, function() use ($concrete) {
return $this->instances[$abstract] ??= $concrete($this);
});
}
public function make(string $abstract) {
if (!isset($this->bindings[$abstract])) {
throw new Exception("未綁定: $abstract");
}
return $this->bindings[$abstract]($this);
}
}
// 配置綁定
$container = new Container();
$container->bind(Calculator::class . ':add', fn() => new Addition());
$container->bind(Calculator::class . ':sub', fn() => new Subtraction());
$container->singleton(Calculator::class . ':alien', fn() => new AlienCalculatorAdapter(new AlienCalculator()));
// 使用
$calculator = $container->make(Calculator::class . ':add');
echo $tester->test($calculator);
?>
| 原則 | 體現 | 收益 |
|---|---|---|
| 介面隔離 | Calculator 只定義 calculate | 實現類職責單一,易擴展新運算 |
| 依賴倒置 | CalculatorTester 依賴介面 | 可注入任意實現,無需修改測試器 |
| 開閉原則 | 新增運算只需新增類 | 不修改現有代碼即可擴展功能 |
| 里氏替換 | 所有實現可互換使用 | 運行時動態切換,支援策略模式 |
| 適配器模式 | 整合不兼容介面 | 復用第三方代碼,不污染核心領域模型 |
<?php
// 測試時輕松 Mock 介面
class MockCalculator implements Calculator {
public function __construct(private float $fixedResult) {}
public function calculate(float $a, float $b): float {
return $this->fixedResult;
}
}
class CalculatorTesterTest extends PHPUnit\Framework\TestCase {
public function testDisplaysResult() {
$tester = new CalculatorTester(10, 5);
$mock = new MockCalculator(42.0);
$output = $tester->test($mock);
$this->assertStringContainsString('42', $output);
}
}
?>
這個計算器案例雖小,但完整演示了面向介面編程的精髓:
interface Calculator 規範「能做什麼」CalculatorTester 只認介面在實際專案中,將此模式應用於:支付網關、存儲驅動、消息隊列、認證提供者、日誌處理器、導出格式 等所有「多實現、可切換、可擴展」的場景。配合依賴注入容器,即可構建高內聚、低耦合、可測試的現代 PHP 應用架構。