组合模式

问题:
在某些场景下,对于某些类而言,一个独立对象和多个对象组成的集合是没有差别的,即,一个独立对象支持的操作,多个对象组成的集合整体也能支持。
当你发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式了。

实现:
1. 类图示例:

2. 代码示例:

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
//单个对象基类
abstract class Unit
{
    //判断是否为对象集合,从而决定是否支持addUnit()和removeUnit()操作
    public function getComposite()
    {
        return null;
    }
    abstract public function bombardStrength();
}
//对象组合基类
abstract class CompositeUnit extends Unit
{
    private $units = [];
    public function getComposite()
    {
        return $this;
    }
    protected function units()
    {
        return $this->units;
    }
    public function removeUnit(Unit $unit)
    {
        $this->units = array_udiff($this->units, array($unit),
            function($a, $b) { return $a == $b ? 0 : 1; });
    }
    public function addUnit(Unit $unit)
    {
        if(in_array($unit, $this->units, true)) {
            return;
        }
        $this->units[] = $unit;
    }
}
//单个对象具体实现
class Archer extends Unit
{
    public function bombardStrength()
    {
        return 4;
    }
}
class LaserCanon extends Unit
{
    public function bombardStrength()
    {
        return 44;
    }
}
//对象组合具体实现
class TroopCarrier extends CompositeUnit
{
    public function bombardStrength()
    {
        $ret = 0;
        foreach($this->units() as $unit) {
            $ret += $unit->bombardStrength();
        }
        return $ret;
    }
}
class Army extends CompositeUnit
{
    public function bombardStrength()
    {
        $ret = 0;
        foreach($this->units() as $unit) {
            $ret += $unit->bombardStrength();
        }
        return $ret;
    }
}
//应用
class UnitScript
{
    static function joinExisting(Unit $newUnit, Unit $occupyingUnit)
    {
        $comp;
        if(!is_null($comp = $occupyingUnit->getComposite())) {
            $comp->addUnit($newUnit);
        } else {
            $comp = new Army();
            $comp->addUnit($occupyingUnit);
            $comp->addUnit($newUnit);
        }
        return $comp;
    }
}
$obj = UnitScript::joinExisting(new Archer(), new LaserCanon());
var_dump($obj);
echo $obj->bombardStrength();

效果:
优点:
1. 组合模式中的组合类和局部类都继承自一个共同的基类,支持一个共同的操作集,这样对于组合结构的操作的复杂性都被完全隐藏了,对于客户端来说,操作组合结构与操作一个单独对象一样的简单。
缺点:
1. 简化的前提是使所有的类都继承同一个基类,简化的好处有时会以降低对象类型安全为代价。
2. 若组合结构中出现某些类不支持某些操作(例如示例中的Archer类和LaserCanon类就不支持addUnit()和removeUnit()操作),则需要手动进行类型检查,做出差异化,当这样的情况越来越多的时候,组合模式就开始显得利大于弊了。只有在大部分局部对象可互换的情况下,组合模式才是最适用的。
3. 组合结构的操作成本可能会是昂贵的。调用组合操作方法,会逐级调用对象树的下级分支的方法(例如示例中的bombardStrength()方法),若下级分支太多,可能会导致系统崩溃。解决办法之一是,在父级对象中缓存计算结果,但需要保证缓存值不会失效,这意味着需要设计一个当对树进行操作时可移除过期缓存的策略,而这也许需要给子对象加上对父对象的引用。
4. 组合模式很难将数据结构存储在关系型数据库中,但非常适合持久化为XML。

组合模式的两种实现方式:
1. 透明方式:所有子类具备完全一致的行为接口,对于外界没有区别(不支持某些操作的子类也需要实现这些操作,只不过可能只是实现抛出异常,没有什么意义)。
2. 安全方式:不支持某些操作的子类则不去实现这些操作,但是客户端的调用需要做相应的判断(代码示例中实现的就是安全方式)。

posted @   疯一样的狼人  阅读(138)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2015-09-10 ThinkPHP缓存
2015-09-10 PHP操作Memcached
点击右上角即可分享
微信分享提示