文章

设计模式全解析:掌握策略模式的精髓

模式定义

策略模式(Strategy Pattern)是行为型设计模式,定义算法家族并封装每个算法,使它们可以互相替换。该模式让算法的变化独立于使用算法的客户端。

核心思想

  1. 算法独立封装:每个算法单独封装成类

  2. 运行时动态切换:客户端可自由选择具体策略

  3. 消除条件分支:避免大量if-else判断逻辑

适用场景

  • 多种算法变体需要灵活切换

  • 需要消除复杂的条件语句

  • 存在相似算法需要统一管理

  • 需要隔离算法实现与使用

模式结构

  • Strategy:策略接口

  • ConcreteStrategy:具体策略实现

  • Context:策略容器和执行环境


PHP实现示例:电商促销策略

<?php
// 促销策略接口
interface PromotionStrategy {
    public function apply(float $price): float;
}

// 具体策略:无优惠
class NoDiscount implements PromotionStrategy {
    public function apply(float $price): float {
        return $price;
    }
}

// 具体策略:满减
class FullReduction implements PromotionStrategy {
    public function apply(float $price): float {
        if ($price >= 200) {
            return $price - 50;
        }
        return $price;
    }
}

// 具体策略:百分比折扣
class PercentageDiscount implements PromotionStrategy {
    public function apply(float $price): float {
        return $price * 0.8;
    }
}

// 策略上下文
class PromotionContext {
    private $strategy;

    public function __construct(PromotionStrategy $strategy) {
        $this->strategy = $strategy;
    }

    public function setStrategy(PromotionStrategy $strategy) {
        $this->strategy = $strategy;
    }

    public function execute(float $price): float {
        return $this->strategy->apply($price);
    }
}

// 客户端使用
$context = new PromotionContext(new NoDiscount());

$prices = [150, 250, 300];
foreach ($prices as $price) {
    // 动态切换策略
    if ($price > 200) {
        $context->setStrategy(new PercentageDiscount());
    } elseif ($price >= 200) {
        $context->setStrategy(new FullReduction());
    }

    $final = $context->execute($price);
    echo "原价:{$price} => 实付:{$final}\n";
}

/* 输出:
原价:150 => 实付:150
原价:250 => 实付:200
原价:300 => 实付:240
*/

Go实现示例:文件压缩策略

package main

import "fmt"

// 压缩策略接口
type CompressionStrategy interface {
	Compress(file string) string
}

// ZIP压缩策略
type ZipStrategy struct{}

func (z *ZipStrategy) Compress(file string) string {
	return fmt.Sprintf("%s.zip", file)
}

// RAR压缩策略
type RarStrategy struct{}

func (r *RarStrategy) Compress(file string) string {
	return fmt.Sprintf("%s.rar", file)
}

// 7z压缩策略
type SevenZStrategy struct{}

func (s *SevenZStrategy) Compress(file string) string {
	return fmt.Sprintf("%s.7z", file)
}

// 压缩上下文
type Compressor struct {
	strategy CompressionStrategy
}

func (c *Compressor) SetStrategy(s CompressionStrategy) {
	c.strategy = s
}

func (c *Compressor) Execute(file string) string {
	if c.strategy == nil {
		return file
	}
	return c.strategy.Compress(file)
}

func main() {
	files := []string{"report.doc", "data.csv", "images"}
	compressor := &Compressor{}

	// 按文件类型选择策略
	for _, file := range files {
		switch {
		case len(file) > 4 && file[len(file)-4:] == ".csv":
			compressor.SetStrategy(&ZipStrategy{})
		case len(file) > 4 && file[len(file)-4:] == ".doc":
			compressor.SetStrategy(&RarStrategy{})
		default:
			compressor.SetStrategy(&SevenZStrategy{})
		}

		result := compressor.Execute(file)
		fmt.Printf("原始文件: %-10s → 压缩结果: %s\n", file, result)
	}
}

/* 输出:
原始文件: report.doc  → 压缩结果: report.doc.rar
原始文件: data.csv   → 压缩结果: data.csv.zip
原始文件: images     → 压缩结果: images.7z
*/

模式优缺点

优点

  • 符合开闭原则,易于扩展新策略

  • 消除复杂的条件判断语句

  • 算法实现与使用解耦

  • 便于单元测试

缺点

  • 策略类数量可能爆炸式增长

  • 客户端必须了解策略差异

  • 增加对象间通信开销


不同语言实现差异

特性

PHP

Go

接口定义

显式interface声明

隐式接口实现

策略绑定

依赖注入+类型约束

接口组合+方法实现

运行时切换

支持动态替换策略

需要重新设置策略对象

典型应用

支付策略、促销方案

压缩算法、网络协议


模式变体与演进

  1. Lambda策略
    使用匿名函数实现轻量级策略(PHP 5.3+/Go 1.18+)

  2. 策略工厂
    结合工厂模式管理策略创建

  3. 组合策略
    多个策略组合使用(如折扣+满减)

  4. 状态模式关联
    策略切换与状态变更联动

  5. 配置化策略
    从配置文件动态加载策略


最佳实践建议

  1. 策略粒度控制
    保持策略的单一职责,避免过度细分

  2. 策略无状态化
    尽量设计无状态的策略类,方便复用

  3. 默认策略设置
    提供合理的默认策略避免空策略

  4. 策略文档规范
    为每个策略编写清晰的文档说明

  5. 性能优化
    对高频使用策略考虑对象池技术


策略模式VS状态模式

维度

策略模式

状态模式

关注点

不同算法的选择

状态驱动的行为变化

切换机制

客户端主动控制

状态变迁自动触发

关系

策略间彼此独立

状态间存在转移关系

典型应用

支付方式选择

订单状态流转


总结

策略模式通过将算法封装为独立对象,实现了算法定义算法使用的彻底解耦。该模式在以下场景表现尤为出色:

  • 需要灵活切换多种算法变体

  • 存在大量条件判断语句

  • 算法需要独立演进和复用

  • 系统需要动态选择算法策略

PHP与Go的实现差异体现了不同语言的哲学:

  • PHP通过接口约束和依赖注入确保策略合规

  • Go利用隐式接口和组合实现更灵活的扩展

实际应用时需要注意:

  • 合理控制策略粒度

  • 避免策略类膨胀

  • 结合其他模式(如工厂模式)优化创建

  • 做好策略的文档管理和版本控制

掌握策略模式的精髓在于理解"委托代替继承"的思想,这种将变化部分抽象封装的设计理念,是构建高扩展性系统的关键所在。

License:  CC BY 4.0