Skip to content

策略模式(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; // 5

2.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

七、全文总结

策略模式的核心是 将算法封装为独立策略,支持运行时切换,消除条件语句

核心要点

  1. 每个算法封装为独立策略类,实现统一接口
  2. 上下文持有策略引用,可动态切换
  3. 是消除 if-else 的有效手段
  4. 可用注册表/工厂模式管理策略
  5. 函数式语言可用高阶函数简化实现