PHP设计模式之组合模式

组合模式也许是将继承用于组合对象的最极端的模式。组合模式的设计思想简单而又非常优雅,并且非常实用。组合模式可以很好地聚合和管理许多相似的对象,因而对客户端代码来说,一个独立对象和一个对象集合是没有差别的。
举个例子:谷物和肉类可以组合成香肠,而香肠是一个组合物。香肠又可以和粉类组合成肉饼,肉饼是一个更大的组合物。所以,组合模式有助于我们为集合和组件之间的关系建立模型。

魔兽争霸兵种攻击力演示第一版:单一部队

Kind:兵种类,strength()攻击力
Swordman:剑士
Gunner:火炮手
Army:军队

此版本可以创建一支或多支军队,可以获取每支军队的总攻击力。但每支军队都是独立的,都由对应的对象管理操作。不能将两个军队合并成一个军队进行统一管理和维护。也无法将合并好的队伍再抽取出来。

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

// 兵种类
abstract class Kind{
abstract public function strength();
}

// 剑士
class Sword extends Kind{
public function strength(){
return 10;
}
}

// 火炮手
class Gunner extends Kind{
public function strength(){
return 28;
}
}

// 陆军部队
class Army{
// 兵种的集合数组
private $kind = [];

// 添加兵种
public function addKind(Kind $kind){
$this->kind[] = $kind;
}

// 获取陆军总攻击力
public function getStrength(){
$strength = 0;
foreach($this->kind as $kind){
$strength += $kind->strength();
}
return $strength;
}
}

$army1 = new Army(); // 创建第一支陆军部队
$army1->addKind(new Sword()); // 添加一名剑士
$army1->addKind(new Sword()); // 添加一名剑士
$army1->addKind(new Sword()); // 添加一名剑士
$army1->addKind(new Gunner()); // 添加一名火炮手
echo '第一支陆军总攻击边为:'.$army1->getStrength(); // 总攻击力

echo '<br />';

$army2 = new Army(); // 创建第一支陆军部队
$army2->addKind(new Sword()); // 添加一名剑士
$army2->addKind(new Sword()); // 添加一名剑士
echo '第二支陆军总攻击边为:'.$army2->getStrength(); // 总攻击力

魔兽争霸兵种攻击力演示第二版:组队与抽离

此版本增加了对军队组合和抽离的功能,目前整个系统还不算太复杂。如果能对单个兵种进行交错组合或者抽离等细节操作,那么如果考虑到高原和低洼地带,或者是夜晚和白天 ,兵种的战斗力会不同,慢慢的系统会变的完善。
但是现在有另外一个需求:军队一般都是陆地上战斗的单位,而我们想要让军队在海上战斗。那么基本上是在船上战斗,这时我们必须创建以船为单位的战斗群。每支舰队只能放5 个兵种,每个兵种在船上战斗,攻击力都会有所下降。剑士下降 2 个点,火炮手下降 5 个点。

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

// 兵种类
abstract class Kind{
abstract public function strength();
}

// 剑士
class Sword extends Kind{
public function strength(){
return 10;
}
}

// 火炮手
class Gunner extends Kind{
public function strength(){
return 28;
}
}

// 陆军部队
class Army{
// 兵种的集合数组
private $kind = [];
//陆军部队的集合数组
private $army = [];

// 添加兵种
public function addKind(Kind $kind){
$this->kind[] = $kind;
}

// 添加陆军部队
public function addArmy(Army $army){
if(!in_array($army, $this->army)){
$this->army[] = $army;
}
}

// 抽取陆军部队
public function removeArmy(Army $army){
if(in_array($army, $this->army)){
$this->army = array_udiff($this->army, [$army],
create_function('$a,$b', 'return ($a === $b) ? 0:1;'));
}
}

// 获取陆军总攻击力
public function getStrength(){
$strength = 0;
foreach($this->army as $army){
$strength += $army->getStrength(); // 这里可以获取组进来的总攻击力
}
foreach($this->kind as $kind){
$strength += $kind->strength();
}
return $strength;
}
}

$army1 = new Army(); // 创建第一支陆军部队
$army1->addKind(new Sword()); // 添加一名剑士
$army1->addKind(new Sword()); // 添加一名剑士
$army1->addKind(new Sword()); // 添加一名剑士
$army1->addKind(new Gunner()); // 添加一名火炮手
echo '第一支陆军总攻击边为:'.$army1->getStrength(); // 总攻击力

echo '<br />';

$army2 = new Army(); // 创建第一支陆军部队
$army2->addKind(new Sword()); // 添加一名剑士
$army2->addKind(new Sword()); // 添加一名剑士
echo '第二支陆军总攻击边为:'.$army2->getStrength(); // 总攻击力

echo '<br />';

$army3 = new Army(); // 创建第三支部队,没有自己的士兵
$army3->addArmy($army1); // 把第一支部队组进来
$army3->addArmy($army2); // 把第二支部队组进来
$army3->addArmy($army1); // 这里会失效,因为组进来了,无需再组
$army3->removeArmy($army1); // 把第一支部队从第三支部队中移除
$army3->removeArmy($army2); // 把第二支部队从第三支部队中移除
$army3->addArmy($army1); // 把第二支部队组进来
echo '第三支陆军总攻击边为:'.$army3->getStrength(); // 总攻击力

魔兽争霸兵种攻击力演示第三版:海上舰队

此版本解决了增加了舰队作战的能力,限制了每个船的人数。降低了兵种的攻击力。但系统开始变的冗余,如果修改或增加一个兵种会发生连锁反应,舰队和军队存在大量的重复操作,最关键的是,军队还不能和舰队组合。所以,整个系统变的不那么灵活了。

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
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
120
121
122
123
124
125
126
127
128
129
130
<?php
header('Content-Type:text/html; charset=utf-8');

// 兵种类
abstract class Kind{
abstract public function strength();
}

// 剑士
class Sword extends Kind{
public function strength(){
return 10;
}
}

// 火炮手
class Gunner extends Kind{
public function strength(){
return 28;
}
}

// 陆军部队
class Army{
// 兵种的集合数组
private $kind = [];
//陆军部队的集合数组
private $army = [];

// 添加兵种
public function addKind(Kind $kind){
$this->kind[] = $kind;
}

// 添加陆军部队
public function addArmy(Army $army){
if(!in_array($army, $this->army)){
$this->army[] = $army;
}
}

// 抽取陆军部队
public function removeArmy(Army $army){
if(in_array($army, $this->army)){
$this->army = array_udiff($this->army, [$army],
create_function('$a,$b', 'return ($a === $b) ? 0:1;'));
}
}

// 获取陆军总攻击力
public function getStrength(){
$strength = 0;
foreach($this->army as $army){
$strength += $army->getStrength(); // 这里可以获取组进来的总攻击力
}
foreach($this->kind as $kind){
$strength += $kind->strength();
}
return $strength;
}
}

// 舰队类
class Warship{
// 兵种的集合数组
private $kind = [];
//舰队的集合数组
private $warship = [];

// 添加兵种
public function addKind(Kind $kind){
if(count($this->kind) < 5){
$this->kind[] = $kind;
}
}

// 添加海军部队
public function addWarship(Warship $warship){
if(!in_array($warship, $this->warship)){
$this->warship[] = $warship;
}
}

// 抽取海军部队
public function removeWarship(Warship $warship){
if(in_array($warship, $this->warship)){
$this->warship = array_udiff($this->warship, [$warship],
create_function('$a,$b', 'return ($a === $b) ? 0:1;'));
}
}

// 获取舰队总攻击力
public function getStrength(){
$strength = 0;
foreach($this->warship as $warship){
$strength += $warship->getStrength(); // 这里可以获取组进来的总攻击力
}
foreach($this->kind as $kind){
if($kind instanceof Sword) $temp = $kind->strength() -2;
if($kind instanceof Gunner) $temp = $kind->strength() - 5;
$strength += $temp;
}
return $strength;
}
}

$warship1 = new Warship();
$warship1->addKind(new Sword());
$warship1->addKind(new Sword());
$warship1->addKind(new Sword());
$warship1->addKind(new Sword());
$warship1->addKind(new Sword());
$warship1->addKind(new Sword()); // 添加第六个士兵无效
echo '第一支海军舰队的总攻击力为:'.$warship1->getStrength();

echo '<br />';

$warship2 = new Warship();
$warship2->addKind(new Gunner());
$warship2->addKind(new Gunner());
$warship2->addKind(new Sword());
echo '第二支海军舰队的总攻击力为:'.$warship2->getStrength();


echo '<br />';

$warship3 = new Warship();
$warship3->addWarship($warship1);
$warship3->addWarship($warship2);
echo '第三支海军舰队的总攻击力为:'.$warship3->getStrength();

魔兽争霸兵种攻击力演示第四版:陆地部队组合海上舰队

此版本使用了组合模式:所有的子类,不管是剑士类、火炮手类、军队类、战舰类 ,都共享一个父类 Kind。也就是说,客户端不必区分这些类,可以任意的组合,而之前的版本,军队只能组合军队,舰队只能组合舰队。而在这里,组合是任意的,不受拘束 。抽取也很方便,并且还可以在一个军队里方便的抽取兵种。

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

// 兵种类
abstract class Kind{
// 兵种的集合数组
protected $kind = [];

// 添加兵种,添加陆军,添加舰队
public function addKind(Kind $kind){
$this->kind[] = $kind;
}

// 抽取兵种,添加陆军,添加舰队
public function removeKind(Kind $kind){
$this->kind = array_udiff($this->kind, [$kind],
create_function('$a,$b', 'return ($a === $b) ? 0:1;'));
}

abstract public function strength();
}

// 剑士
class Sword extends Kind{
public function strength(){
return 10;
}
}

// 火炮手
class Gunner extends Kind{
public function strength(){
return 28;
}
}

// 陆军类
class Army extends Kind{
// 获取陆军总攻击力
public function strength(){
$strength = 0;
foreach($this->kind as $kind){
$strength += $kind->strength();
}
return $strength;
}
}

// 舰队
class Warship extends Kind{
// 获取舰队总攻击力
public function strength(){
$strength = 0;
foreach($this->kind as $kind){
$strength += $kind->strength();
if($kind instanceof Sword) $strength -= 2;
if($kind instanceof Gunner) $strength -= 5;
}
return $strength;
}
}

$army1 = new Army(); //创建第一支陆军部队
$army1->addKind(new Sword()); //添加一名剑士10
$army1->addKind(new Sword()); //添加一名剑士10
$army1->addKind(new Sword()); //添加一名剑士10
$army1->addKind(new Gunner()); //添加一名火炮手28
echo '第一支陆军总攻击力为:'.$army1->strength(); //总攻击力

echo '<br />';

$army2 = new Army(); //创建第一支陆军部队
$army2->addKind(new Sword()); //添加一名剑士10
$army2->addKind(new Sword()); //添加一名剑士10
echo '第二支陆军总攻击力为:'.$army2->strength(); //总攻击力

echo '<br />';

$army3 = new Army(); //创建第三支部队,没有自己的士兵
$army3->addKind($army1); //把第一支部队组进来
$army3->addKind($army2); //把第二支部队组进来
echo '第三支陆军总攻击力为:'.$army3->strength(); //总攻击力

echo '<br />';

$warship1 = new Warship();
$warship1->addKind(new Sword());
$warship1->addKind(new Sword());
$warship1->addKind(new Sword());
$warship1->addKind(new Sword());
echo '第一支海军舰队的总攻击力为:'.$warship1->strength();

echo '<br />';

$army4 = new Warship();
$army4->addKind($army3);
$army4->addKind($warship1);
$army4->addKind(new Gunner());
echo '第一支陆军+海军舰队混合战队的总攻击力为:'.$army4->strength();

组合模式的特点:

  1. 所有的子类都共享一个父类。
  2. 你可以优化处理递归或分级数据结构。
  3. 用户无视对象的不同,统一的使用组合模式中的所有对象。