初识设计模式——策略模式(Strategy Pattern)

cuixiaogang

策略模式(Strategy Pattern)是一种行为型设计模式,其核心思想是将算法的定义与使用分离,允许在运行时动态选择不同的算法策略。

策略模式定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式使得算法的变化,不会影响到使用算法的客户。

策略模式的组成

策略接口(Strategy)

定义所有支持算法的公共接口,声明算法执行的方法

1
2
3
4
5
<?php
// 定义支付策略接口
interface PaymentStrategy {
public function pay($amount);
}

具体策略类(Concrete Strategy)

实现策略接口,提供具体的算法实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 支付宝支付策略
class AlipayStrategy implements PaymentStrategy {
public function pay($amount) {
echo "使用支付宝支付:{$amount} 元\n";
}
}

// 微信支付策略
class WeChatPayStrategy implements PaymentStrategy {
public function pay($amount) {
echo "使用微信支付:{$amount} 元\n";
}
}

上下文类(Context)

持有一个策略对象的引用,负责在运行时动态切换策略,并委托策略执行算法(策略模式名称的来源,需要在这里定义策略)。

1
2
3
4
5
6
7
8
9
10
11
12
// 订单类,作为上下文类
class Order {
private $paymentStrategy;

public function setPaymentStrategy(PaymentStrategy $strategy) {
$this->paymentStrategy = $strategy;
}

public function pay($amount) {
$this->paymentStrategy->pay($amount);
}
}

客户端使用示例

1
2
3
4
5
6
7
8
9
10
// 客户端代码
$order = new Order();

// 选择支付宝支付
$order->setPaymentStrategy(new AlipayStrategy());
$order->pay(299.99);

// 切换为微信支付
$order->setPaymentStrategy(new WeChatPayStrategy());
$order->pay(199.99);

策略模式的优缺点

策略模式的优点

  • 灵活性与扩展性:新增策略时只需实现接口,无需修改原有代码(开闭原则)。例如新增微信支付策略,只需添加WeChatPayStrategy类。
  • 减少条件判断:避免在客户端使用大量if-else或switch-case语句,将条件逻辑转移到策略类中。
  • 运行时动态切换:可根据业务需求在运行时灵活选择不同策略。例如电商订单可根据用户选择切换支付方式。
  • 提高代码复用性:不同策略可被多个上下文共享,减少重复代码。

策略模式的缺点

  • 客户端需了解策略细节:客户端必须知道所有策略类并决定使用哪一个,可能增加调用复杂度。
  • 类数量增加:每个策略对应一个类,当策略较多时会导致系统类膨胀。
  • 性能开销:策略对象可能需要频繁创建和销毁,若策略类较重(如包含大量数据),可能影响性能。

应用场景

  • 算法动态切换:如支付方式、排序算法、加密策略等。
  • 行为型功能扩展:避免通过继承扩展功能时的类爆炸问题。
  • 消除条件判断:将复杂的条件逻辑抽取为独立策略。

策略模式与简单工厂模式的区别

策略模式和简单工厂模式都是软件开发中常用的设计模式,但它们有着不同的侧重点和应用场景

模式定义和核心意图

  • 策略模式
    • 定义:定义一系列的算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
    • 核心意图:强调行为的封装和可替换性,旨在解决在多种算法实现中,客户端能够根据不同情况动态选择合适的算法,而不需要修改客户端代码。
  • 简单工厂模式
    • 定义:定义一个创建对象的类,由这个类来封装实例化对象的行为。
    • 核心意图:将对象的创建和使用分离,客户端只需要通过工厂类获取所需的对象,而不需要关心对象的具体创建过程,提高代码的可维护性和可扩展性。

结构组成

  • 策略模式
    • 策略接口(Strategy):定义所有支持算法的公共接口,声明算法执行的方法。
    • 具体策略类(Concrete Strategy):实现策略接口,提供具体的算法实现。
    • 上下文类(Context):持有一个策略对象的引用,负责在运行时动态切换策略,并委托策略执行算法。
  • 简单工厂模式
    • 工厂类(Factory):负责创建对象的类,根据传入的参数决定创建哪种具体的产品对象。
    • 抽象产品类(Product):定义产品的公共接口或抽象类。
    • 具体产品类(Concrete Product):实现抽象产品类的具体产品。

应用场景

  • 策略模式
    • 当一个系统需要在多种算法中动态选择一种时,例如电商系统中的不同支付方式、游戏中的不同角色技能实现等。
    • 当有多个类的区别仅在于它们的行为时,可以使用策略模式将这些行为封装成不同的策略类,以减少代码的重复。
  • 简单工厂模式
    • 当创建对象的逻辑比较复杂,且客户端不希望了解对象的创建细节时,例如数据库连接对象的创建、文件读取器对象的创建等。
    • 当一个类需要根据不同的条件创建不同类型的对象时,使用简单工厂模式可以将创建逻辑集中在工厂类中,提高代码的可维护性。

侧重点

  • 策略模式
    • 侧重于算法的封装和切换,强调行为的变化和可替代性,客户端可以在运行时动态改变策略。
    • 关注的是如何在不同的算法实现之间进行灵活选择和切换。
  • 简单工厂模式
    • 侧重于对象的创建过程,将对象的创建逻辑封装在工厂类中,客户端只需要使用工厂类获取对象,而不需要关心对象的具体创建细节。
    • 关注的是如何将对象的创建和使用分离,提高代码的可维护性和可扩展性。

策略模式与简单工厂模式结合使用的案例

在代码开发中,可以将策略的选择权交给简单工厂类,让它来选择创建某个对象,这样方式可以解决策略模式中客户端需了解策略细节的缺点。以下使用一个API响应格式的需求来作为案例

策略模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php

// 策略类接口
interface ResponseStrategy
{
function getData($data);
}

// 具体策略类 XmlData
class XmlData implements ResponseStrategy
{
public function getData($data)
{
$options = [
// 根节点名
'root_node' => 'strategy',
// 根节点属性
'root_attr' => '',
//数字索引的子节点名
'item_node' => 'item',
// 数字索引子节点key转换的属性名
'item_key' => 'id',
// 数据编码
'encoding' => 'utf-8',
];
$data = $this->xmlEncode($data, $options['root_node'], $options['item_node'], $options['root_attr'], $options['item_key'], $options['encoding']);
ob_end_clean();
header('Content-Type:text/xml');
echo $data;die;
}
protected function xmlEncode($data, $root, $item, $attr, $id, $encoding)
{
if (is_array($attr)) {
$array = [];
foreach ($attr as $key => $value) {
$array[] = "{$key}=\"{$value}\"";
}
$attr = implode(' ', $array);
}
$attr = trim($attr);
$attr = empty($attr) ? '' : " {$attr}";
$xml = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
$xml .= "<{$root}{$attr}>";
$xml .= $this->dataToXml($data, $item, $id);
$xml .= "</{$root}>";
return $xml;
}
protected function dataToXml($data, $item, $id)
{
$xml = $attr = '';
if ($data instanceof Collection || $data instanceof Model) {
$data = $data->toArray();
}

foreach ($data as $key => $val) {
if (is_numeric($key)) {
$id && $attr = " {$id}=\"{$key}\"";
$key = $item;
}
$xml .= "<{$key}{$attr}>";
$xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val;
$xml .= "</{$key}>";
}
return $xml;
}
}

// 具体策略类 JsonData
class JsonData implements ResponseStrategy
{
public function getData($data)
{
ob_end_clean();
header('Content-Type:application/json');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
die;
}
}

// 客户端(策略模式)
Class Client
{
public static function index($type)
{
$data = [
['name' => 'renling', 'age' => '18', 'sex' => '1'],
['name' => 'renling', 'age' => '18', 'sex' => '1'],
['name' => 'renling', 'age' => '18', 'sex' => '1'],
['name' => 'renling', 'age' => '18', 'sex' => '1']
];
switch ($type) {
case 'json':
$server = new JsonData();
break;
case 'xml':
$server = new XmlData();
break;
default :
$server = null;
break;
}
if (empty($server)) {
return '';
} else {
$server->getData($data);
}
}
}
$type = isset($_GET['type'])?$_GET['type']:'json';
Client::index($type);

结合简单工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<?php

// 策略类接口
interface ResponseStrategy
{
function getData($data);
}

// 具体策略类 XmlData
class XmlData implements ResponseStrategy
{
public function getData($data)
{
$options = [
// 根节点名
'root_node' => 'strategy',
// 根节点属性
'root_attr' => '',
//数字索引的子节点名
'item_node' => 'item',
// 数字索引子节点key转换的属性名
'item_key' => 'id',
// 数据编码
'encoding' => 'utf-8',
];
$data = $this->xmlEncode($data, $options['root_node'], $options['item_node'], $options['root_attr'], $options['item_key'], $options['encoding']);
ob_end_clean();
header('Content-Type:text/xml');
echo $data;die;
}
protected function xmlEncode($data, $root, $item, $attr, $id, $encoding)
{
if (is_array($attr)) {
$array = [];
foreach ($attr as $key => $value) {
$array[] = "{$key}=\"{$value}\"";
}
$attr = implode(' ', $array);
}
$attr = trim($attr);
$attr = empty($attr) ? '' : " {$attr}";
$xml = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
$xml .= "<{$root}{$attr}>";
$xml .= $this->dataToXml($data, $item, $id);
$xml .= "</{$root}>";
return $xml;
}
protected function dataToXml($data, $item, $id)
{
$xml = $attr = '';
if ($data instanceof Collection || $data instanceof Model) {
$data = $data->toArray();
}

foreach ($data as $key => $val) {
if (is_numeric($key)) {
$id && $attr = " {$id}=\"{$key}\"";
$key = $item;
}
$xml .= "<{$key}{$attr}>";
$xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val;
$xml .= "</{$key}>";
}
return $xml;
}
}

// 具体策略类 JsonData
class JsonData implements ResponseStrategy
{
public function getData($data)
{
ob_end_clean();
header('Content-Type:application/json');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
die;
}
}

// 简单工厂类
class Factory
{
public static function getData($data, $type = 'json')
{
switch ($type) {
case 'json':
$server = new JsonData();
break;
case 'xml':
$server = new XmlData();
break;
default :
$server = null;
break;
}
if (empty($server)) {
return '';
} else {
$server->getData($data);
}
}
}

// 客户端(使用简单工厂模式优化)
Class Client
{
public static function index($type)
{
$data = [
['name' => 'renling', 'age' => '18', 'sex' => '1'],
['name' => 'renling', 'age' => '18', 'sex' => '1'],
['name' => 'renling', 'age' => '18', 'sex' => '1'],
['name' => 'renling', 'age' => '18', 'sex' => '1']
];
Factory::getData($data, $type);
}
}
$type = isset($_GET['type'])?$_GET['type']:'json';
Client::index($type);

UML类图变化

策略模式UML类图

策略模式结合简单工厂模式的UML类图