策略模式(Strategy)
前言
策略模式定义一系列算法,将每个算法封装起来,使它们可以相互替换。它是消除大量 if-else 的利器,让算法的变化独立于使用算法的客户端。本文将详细讲解策略模式的核心原理及实际应用。
一、核心概念
1.1 定义
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的客户端而变化。
1.2 核心角色
| 角色 | 说明 |
|---|---|
| 策略接口(Strategy) | 定义算法接口 |
| 具体策略(ConcreteStrategy) | 实现具体算法 |
| 上下文(Context) | 持有策略引用,委托策略执行 |
二、代码实现
2.1 基础实现
php
<?php
// 策略接口
interface Strategy
{
public function execute(array $data): int|float;
}
// 求和策略
class SumStrategy implements Strategy
{
public function execute(array $data): int|float
{
return array_sum($data);
}
}
// 求平均策略
class AverageStrategy implements Strategy
{
public function execute(array $data): int|float
{
if (count($data) === 0) return 0;
return array_sum($data) / count($data);
}
}
// 求最大值策略
class MaxStrategy implements Strategy
{
public function execute(array $data): int|float
{
return max($data);
}
}
// 上下文:持有策略引用
class Context
{
private Strategy $strategy;
public function __construct(Strategy $strategy)
{
$this->strategy = $strategy;
}
public function setStrategy(Strategy $strategy): void
{
$this->strategy = $strategy;
}
public function execute(array $data): int|float
{
return $this->strategy->execute($data);
}
}
// 使用
$data = [1, 2, 3, 4, 5];
$context = new Context(new SumStrategy());
echo "Sum: " . $context->execute($data) . PHP_EOL; // 15
$context->setStrategy(new AverageStrategy());
echo "Average: " . $context->execute($data) . PHP_EOL; // 3
$context->setStrategy(new MaxStrategy());
echo "Max: " . $context->execute($data) . PHP_EOL; // 52.2 实际案例:支付方式选择
php
<?php
// 支付策略接口
interface PaymentStrategy
{
public function pay(float $amount): bool;
}
// 信用卡支付
class CreditCardPayment implements PaymentStrategy
{
public function __construct(
private string $name,
private string $cardNumber,
private string $cvv
) {}
public function pay(float $amount): bool
{
echo "💳 Processing \${$amount} via Credit Card ({$this->name})" . PHP_EOL;
return true;
}
}
// 支付宝支付
class AlipayPayment implements PaymentStrategy
{
public function __construct(private string $email) {}
public function pay(float $amount): bool
{
echo "💰 Processing \${$amount} via Alipay ({$this->email})" . PHP_EOL;
return true;
}
}
// 微信支付
class WeChatPayment implements PaymentStrategy
{
public function __construct(private string $openId) {}
public function pay(float $amount): bool
{
echo "💚 Processing \${$amount} via WeChat Pay" . PHP_EOL;
return true;
}
}
// 支付上下文
class PaymentContext
{
private ?PaymentStrategy $strategy = null;
public function setStrategy(PaymentStrategy $strategy): void
{
$this->strategy = $strategy;
}
public function pay(float $amount): bool
{
if ($this->strategy === null) {
throw new RuntimeException("No payment strategy set");
}
return $this->strategy->pay($amount);
}
}
// 使用
$payment = new PaymentContext();
$payment->setStrategy(
new CreditCardPayment("Flynn", "1234-5678-9012-3456", "123")
);
$payment->pay(100);
$payment->setStrategy(new AlipayPayment("flynn@example.com"));
$payment->pay(50);2.3 实际案例:排序策略
php
<?php
// 排序策略接口(PHP 7.4+ 支持类型系统,泛型用 PHPDoc 标注)
interface SortStrategy
{
/**
* @param array<int, int|float> $data
* @return array<int, int|float>
*/
public function sort(array $data): array;
}
// 冒泡排序
class BubbleSort implements SortStrategy
{
/** @var callable(int|float, int|float): int */
private $compare;
public function __construct(callable $compare)
{
$this->compare = $compare;
}
public function sort(array $data): array
{
$result = array_values($data);
$n = count($result);
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n - $i - 1; $j++) {
if (($this->compare)($result[$j], $result[$j + 1]) > 0) {
[$result[$j], $result[$j + 1]] = [$result[$j + 1], $result[$j]];
}
}
}
return $result;
}
}
// 快速排序
class QuickSort implements SortStrategy
{
/** @var callable(int|float, int|float): int */
private $compare;
public function __construct(callable $compare)
{
$this->compare = $compare;
}
public function sort(array $data): array
{
if (count($data) <= 1) return $data;
$pivot = $data[0];
$rest = array_slice($data, 1);
$left = array_values(array_filter($rest, fn($x) => ($this->compare)($x, $pivot) <= 0));
$right = array_values(array_filter($rest, fn($x) => ($this->compare)($x, $pivot) > 0));
return array_merge($this->sort($left), [$pivot], $this->sort($right));
}
}
// 使用
$numbers = [5, 2, 8, 1, 9, 3];
$comparator = fn($a, $b) => $a <=> $b;
$bubble = new BubbleSort($comparator);
echo "Bubble: " . implode(",", $bubble->sort($numbers)) . PHP_EOL;
$quick = new QuickSort($comparator);
echo "Quick: " . implode(",", $quick->sort($numbers)) . PHP_EOL;2.4 消除 if-else
php
<?php
// 问题:大量 if-else
class BadDiscountCalculator
{
public function calculate(string $type, float $price): float
{
if ($type === "normal") return $price;
elseif ($type === "vip") return $price * 0.8;
elseif ($type === "svip") return $price * 0.6;
elseif ($type === "employee") return $price * 0.5;
else return $price;
}
}
// 使用策略模式重构
interface DiscountStrategy
{
public function calculate(float $price): float;
}
class NormalDiscount implements DiscountStrategy
{
public function calculate(float $price): float
{
return $price;
}
}
class VipDiscount implements DiscountStrategy
{
public function calculate(float $price): float
{
return $price * 0.8;
}
}
class SvipDiscount implements DiscountStrategy
{
public function calculate(float $price): float
{
return $price * 0.6;
}
}
// 使用关联数组注册策略
/** @var array<string, DiscountStrategy> */
$strategies = [
"normal" => new NormalDiscount(),
"vip" => new VipDiscount(),
"svip" => new SvipDiscount(),
];
function calculateDiscount(string $type, float $price): float
{
global $strategies;
if (!isset($strategies[$type])) {
throw new RuntimeException("Unknown discount type: {$type}");
}
return $strategies[$type]->calculate($price);
}三、适用场景
| 场景 | 说明 |
|---|---|
| 多种算法选择 | 排序、搜索、计算 |
| 消除条件语句 | 替代 if-else/switch |
| 支付方式 | 不同支付策略 |
| 折扣/促销 | 不同折扣规则 |
| 验证规则 | 不同验证策略 |
| 路由策略 | 不同路由算法 |
四、优缺点分析
| 优点 | 缺点 |
|---|---|
| 消除条件语句 | 需要创建多个策略类 |
| 支持动态切换 | 增加代码复杂度 |
| 符合开闭原则 | 客户端需了解策略差异 |
| 算法独立变化 | 策略选择逻辑仍需处理 |
| 易于测试 | 简单场景过度设计 |
五、常见踩坑与问题排查
5.1 策略选择逻辑未消除
php
<?php
// 问题:仍然用 if-else 选择策略
function getStrategy(string $type): Strategy
{
if ($type === "A") return new StrategyA();
if ($type === "B") return new StrategyB();
// ...
}
// 解决:使用注册表
class StrategyRegistry
{
/** @var array<string, callable(): Strategy> */
private array $strategies = [];
public function register(string $name, callable $factory): void
{
$this->strategies[$name] = $factory;
}
public function get(string $name): Strategy
{
if (!isset($this->strategies[$name])) {
throw new RuntimeException("Unknown strategy: {$name}");
}
return ($this->strategies[$name])();
}
}5.2 策略类过多
php
<?php
// 问题:策略类太多
// 解决:使用闭包策略
/** @var array<string, callable(array<int, int|float>): int|float> */
$strategies = [
'sum' => fn(array $data): int|float => array_sum($data),
'max' => fn(array $data): int|float => empty($data) ? 0 : max($data),
'average' => function (array $data): int|float {
return empty($data) ? 0 : array_sum($data) / count($data);
},
];六、优化方案与进阶
6.1 结合工厂模式
php
<?php
class StrategyFactory
{
public static function create(string $type): Strategy
{
$map = [
'sum' => SumStrategy::class,
'average' => AverageStrategy::class,
'max' => MaxStrategy::class,
];
if (!isset($map[$type])) {
throw new RuntimeException("Unknown: {$type}");
}
$class = $map[$type];
return new $class();
}
}6.2 函数式策略
php
<?php
// 使用高阶函数实现策略
$createDiscount = fn(float $rate) => fn(float $price) => $price * $rate;
$discounts = [
'normal' => $createDiscount(1),
'vip' => $createDiscount(0.8),
'svip' => $createDiscount(0.6),
];
echo $discounts['vip'](100) . PHP_EOL; // 80七、全文总结
策略模式的核心是 将算法封装为独立策略,支持运行时切换,消除条件语句。
核心要点:
- 每个算法封装为独立策略类,实现统一接口
- 上下文持有策略引用,可动态切换
- 是消除 if-else 的有效手段
- 可用注册表/工厂模式管理策略
- 函数式语言可用高阶函数简化实现
