PHP设计模式之策略模式

策略模式可以用来创建可插入、可替换、可重用的组件。
本文章使用一个课程销售系统的例子来说明策略模式的使用方法及特点。

只有一个 Lesson 类的课程销售系统

demo1.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

//课程类
class Lesson{
// 课程购买人数
private $num;
// 课程的类型
private $type;
// 英文课的标识
const ENGLISH = 1;
// 数学课的标识
const MATH = 2;

// 构造方法初始化
public function __construct($num, $type){
$this->num = $num;
$this->type = $type;
}

// 返回课程所需的费用
public function cost(){
switch($this->type){
case self::ENGLISH:
return 300 * $this->num;
case self::MATH:
return 180 * $this->num;
break;
}
}

// 返回购买的课程
public function type(){
switch($this->type){
case self::ENGLISH:
return '您购买的是英文课';
break;
case self::MATH:
return '您购买的是数学课';
break;
}
}
}

$lesson = new Lesson(5, Lesson::ENGLISH);
echo $lesson->type().$lesson->cost();

echo '<br />';

$lesson = new Lesson(2, Lesson::MATH);
echo $lesson->type().$lesson->cost();

使用了继承方法让子类做具体的实现

demo2.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

// 抽象课程类
abstract class Lesson{
// 课程购买人数
protected $num;

// 构造方法初始化
public function __construct($num){
$this->num = $num;
}

// 返回课程所需的费用
abstract public function cost();

// 返回购买的课程
abstract public function type();
}

class English extends Lesson{
public function cost(){
return 300 * $this->num;
}

public function type(){
return '您购买的是英文课';
}
}

class Math extends Lesson{
public function cost(){
return 180 * $this->num;
}

public function type(){
return '您购买的是数学课';
}
}

$lesson = new English(5);
echo $lesson->type().$lesson->cost();

echo '<br />';

$lesson = new Math(2);
echo $lesson->type().$lesson->cost();

使用了组合的策略模式来代替继承

策略模式的组成

  1. 抽象策略角色:策略类,通常由一个接口或者抽象类实现。
  2. 具体策略角色:包装了相关的算法和行为。
  3. 环境角色:持有一个策略类的引用,最终给客户端调用。

demo3.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

class Lesson{
// 课程购买人数
private $num;
// 策略属性字段,保存具体策略角色对象的引用,例如English对象或者Math对象
private $_strategy;

public function __get($key){
return $this->$key;
}

// 构造方法初始化
public function __construct($num,$_strategy){
$this->num = $num;
$this->_strategy = $_strategy;
}

// 返回具体策略角色课程所需的费用
public function cost(){
return $this->_strategy->cost($this);
}

// 返回具体策略角色购买的课程
public function type(){
return $this->_strategy->type();
}
}

class English{
public function cost(Lesson $lesson){
return 300 * $lesson->num;
}

public function type(){
return '您购买的是英文课';
}
}

class Math{
public function cost(Lesson $lesson){
return 180 * $lesson->num;
}

public function type(){
return '您购买的是数学课';
}
}

$lesson = new Lesson(5, new English()); // 通过不同的参数,来改变不同的课程的行为,这种方法实现了类切换就是多态。
echo $lesson->type().$lesson->cost();

echo '<br />';

$lesson = new Lesson(2, new Math());
echo $lesson->type().$lesson->cost();

添加一个父类来规范所有子类

demo4.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

class Lesson{
// 课程购买人数
private $num;
// 策略属性字段,保存具体策略角色对象的引用,例如English对象或者Math对象
private $_strategy;

public function __get($key){
return $this->$key;
}

// 构造方法初始化
public function __construct($num,$_strategy){
$this->num = $num;
$this->_strategy = $_strategy;
}

// 返回具体策略角色课程所需的费用
public function cost(){
return $this->_strategy->cost($this);
}

// 返回具体策略角色购买的课程
public function type(){
return $this->_strategy->type();
}
}

abstract class L{
abstract public function cost(Lesson $lesson);
abstract public function type();
}

class English extends L{
public function cost(Lesson $lesson){
return 300 * $lesson->num;
}

public function type(){
return '您购买的是英文课';
}
}

class Math extends L{
public function cost(Lesson $lesson){
return 180 * $lesson->num;
}

public function type(){
return '您购买的是数学课';
}
}

$lesson = new Lesson(5, new English()); // 通过不同的参数,来改变不同的课程的行为,这种方法实现了类切换就是多态。
echo $lesson->type().$lesson->cost();

echo '<br />';

$lesson = new Lesson(2, new Math());
echo $lesson->type().$lesson->cost();

笨拙的继承

demo5.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

abstract class Lesson{
//课程购买人数
protected $num;

//构造方法初始化
public function __construct($num) {
$this->num = $num;
}

//返回课程所需的费用
abstract public function cost();

//返回购买的课程
abstract public function type();
}

abstract class Arts extends Lesson{
protected $_discount = 0.85;
protected $type = '文科';
}

abstract class Science extends Lesson{
protected $_discount = 0.75;
protected $type = '理科';
}


class EnglishArts extends Arts{
public function cost(){
return $this->_discount * 300 * $this->num;
}

public function type(){
return '您购买的是'.$this->type.'英文课';
}
}

class MathArts extends Arts{
public function cost(){
return $this->_discount * 180 * $this->num;
}

public function type(){
return '您购买的是'.$this->type.'数学课';
}
}

class EnglishScience extends Science{
public function cost(){
return $this->_discount * 300 * $this->num;
}

public function type(){
return '您购买的是'.$this->type.'英文课';
}
}

class MathScience extends Science{
public function cost(){
return $this->_discount * 180 * $this->num;
}

public function type(){
return '您购买的是'.$this->type.'数学课';
}
}

$lesson = new EnglishArts(5);
echo $lesson->type().$lesson->cost();

echo '<br />';

$lesson = new MathScience(5);
echo $lesson->type().$lesson->cost();

灵活性扩展性极高的策略模式

策略模式的代码要少于继承方式的代码,在未来不断扩展和组合上,代码更少。打个不恰当的比方,同样的功能,如果继承要 100 类实现,那么策略模式只要 30 个就可以完成。而策略模式在重新组装程序,插入和替换功能非常的优秀,非常轻便,而继承则臃肿不堪,随时都会让维护他的程序员休克。
总结:继承要优于分支条件判断,因为继承可以降低了耦合,提升了内聚。但在需求变化后,或者在功能不断扩展后,继承就显得有些臃肿。那么组合方式的策略模式要优于继承 ,因为他灵活,将子类的实现算法高度内聚,高度独立,降低了各个类的关联耦合,所以,策略模式易于扩展和维护。
策略模式优缺点
优点:

  1. 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更
    灵活(算法独立,可以任意扩展)。
  2. 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
  3. 遵守大部分 GRASP 原则和常用设计原则,高内聚、低偶合。
    缺点:
  4. 因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。

demo6.php

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
<?php
header('Content-Type:text/html; charset=utf-8');

//课程类
abstract class Lesson {
//课程购买人数
private $num;
//策略属性字段,保存具体策略角色对象的引用,例如English对象或者Math对象
private $_strategy;

//构造方法初始化
public function __construct($num, $_strategy) {
$this->num = $num;
$this->_strategy = $_strategy;
}

//拦截器
public function __get($key) {
return $this->$key;
}

//返回具体策略角色课程所需的费用
public function cost() { //$this表示Lesson类,传递给English
return $this->_strategy->cost($this); //$this->_strategy保存了English对象(new English())
}

//返回具体策略角色购买的课程
public function type() {
return $this->_strategy->type($this);
}

}

class Arts extends Lesson {
protected $_discount = 0.85; //父类对象访问子类属性
protected $type = '文科';
}

class Science extends Lesson {
protected $_discount = 0.75;
protected $type = '理科';
}


abstract class L {
abstract public function cost(Lesson $lesson);
abstract public function type(Lesson $lesson);
}

class English extends L {
public function cost(Lesson $lesson) { //父类对象访问子类属性
return $lesson->_discount * 300 * $lesson->num;
}
public function type(Lesson $lesson) {
return '您购买的是'.$lesson->type.'英语课程!';
}
}

class Math extends L {
public function cost(Lesson $lesson) {
return $lesson->_discount * 180 * $lesson->num;
}
public function type(Lesson $lesson) {
return '您购买的是'.$lesson->type.'数学课程!';
}
}

$lesson = new Arts(5,new English());
echo $lesson->type().$lesson->cost();

echo '<br />';

$lesson = new Science(2,new Math());
echo $lesson->type().$lesson->cost();