“面向对象编程”是对现实世界的抽象,是通过代码构建出一个虚拟的世界。

    如下图所示,这是一个歌唱比赛节目的游戏规则示例(为便于举例,采用了大家耳熟能详的歌手名称,不代表歌手的演艺实力),如何在虚拟世界中通过代码实现这样的游戏规则呢?

组合模式配图1.jpg

    上图表述了游戏的三个阶段:

        1. 16进8淘汰赛:由16名参赛者一一对决,票数优先者晋级下一轮比赛,票数落后者被淘汰。

        2. 8进4晋级赛:由8名参赛者一一对决,票数优先者晋级到本赛区决赛。

        3. 赛区争夺战:由前两个阶段产生出本赛区冠亚军共2名,组成赛区战队,与另一赛区战队对决。对决票数优先战队将获得总决赛的额外加分权。

    为了简化示例,后续比赛规则在这里不展开描述了。值得一提的是:每一个参赛单元可以是独立的一个人,也可以是由多个人形成的组合,如上图所示的F4和动力火车,则是分别由4人和2人形成的组合。并且在比赛的第3阶段,是由2个参赛单元组成的战队。

    那么问题来了,参与比赛的“单元”会出现多种不同的情况:

        1. 独立的一个人

        2. 多个人形成的组合

        3. 2个参赛单元组成的战队

            3A. 独立的一个人和独立的一个人,组成战队

            3B. 独立的一个人和多个人形成的组合,组成战队

            3C. 组合和组合,组成战队


针对这种情况,可以采用组合模式来对代码构造的虚拟世界进行建模。


首先建立最小参赛单元:Unit

// 参赛单元
public class Unit
{
    // 参赛单元 名称
    public string Name { get; set; }

    // 获得票选数量
    public int NumberOfVotes { get; set; }

    // 子级单元
    // 若为多人形成的组合,则此属性为形成组合的各个人
    public List<Unit> SubUnits { get; set; }
}


再建立两个参赛单元的对决函数:Battle

// 两个参赛单元对决,得到获胜的一方
public Unit Battle(Unit unitA, Unit unitB)
{
    if (unitA.NumberOfVotes > unitB.NumberOfVotes)    // 若A参赛单元的票选数量多于B
        return unitA;                                 // 则判定A胜利
    return unitB;                                     // 否则判定B胜利

    // 总票数为奇数计票可采用此方法,总票数为偶数计票可能出现两个参赛单元票数相同的情况,故不适用于此方法
}


最后建立模型,调用对决函数Battle,得到对决结果

var guo = new Unit { Name = "郭富城", NumberOfVotes = 800 };	// 这是郭富城,800票
var f4 = new Unit
{
    Name = "F4",                                                // 这是F4
    NumberOfVotes = 799,                                        // F4有799票
    SubUnits = new List<Unit>					// F4组合由以下这些成员组成
    {
    	new Unit{Name = "言承旭"},
        new Unit{Name = "吴建豪"},
        new Unit{Name = "周渝民"},
        new Unit{Name = "朱孝天"},
    }
};
var winnerOfElimination = Battle(guo, f4); 			// 郭富城800多余F4的799,郭富城获胜

var red = new Unit
{
    Name = "红方战队",						// 这是红方战队
    NumberOfVotes = 998,					// 红方战队有998票
    SubUnits = new List<Unit>					// 红方战队由以下这些成员组成
    {
        guo,
        new Unit{ Name="李宗盛"}
    }
};
var blue = new Unit
{
    Name = "蓝方战队",						// 这是蓝方战队
    NumberOfVotes = 999,					// 蓝方战队有999票
    SubUnits = new List<Unit>					// 蓝方战队由以下这些成员组成
    {
        new Unit{Name = "张学友"},
        new Unit{Name = "张信哲"},
    }
};
var winnerOfDivision = Battle(red, blue); 			// 红方战队998少于蓝方战队999票,蓝方获胜


若战队由独立一个人和组合形成,那么这个战队的模型即为:

var team = new Unit
{
    Name = "独立一个人和组合形成的战队",
    NumberOfVotes = 888,
    SubUnits = new List<Unit>
    {
        new Unit
        {
            Name = "F4",
            SubUnits = new List<Unit>
            {
                new Unit{Name = "言承旭"},
                new Unit{Name = "吴建豪"},
                new Unit{Name = "周渝民"},
                new Unit{Name = "朱孝天"},
            }
        },
        new Unit
        {
            Name = "李宗盛"
        }
    }
};

 

    至此,我们已经可以通过组合模式,在虚拟世界中构建一个简单的歌唱比赛节目的游戏规则。

    由以上组合模式中的数据模型,我们不难看出它独有的特性,即:对象下面的子级属性,和对象本身是同一类型。

组合模式配图2.png

    所以组合模式中的对象,其下级对象、或下级对象集合,都是可以向再下级无限延伸的。并且单个对象和多个对象形成的组合,在业务层面上被同等对待,他们具有一致性。它常被我们用在树形菜单、多级分类、企业组织架构等功能实现上。


    好了,组合模式介绍到这里,留下一个问题供读者思考:

    组合模式常被使用在企业组织架构的功能实现上,那么一个如下图所示的组织架构,我们如何遍历得到“技术部”这一个架构节点?你的遍历算法如何让“时间复杂度”达到最优?

组合模式配图3-组织架构图.jpg