部分整体,树形结构,『组合模式』来帮忙

目录:设计模式之小试牛刀
源码路径:Github-Design Pattern


定义:(Composite Pattern)

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
又称为部分整体模式,合成模式。

类图:

组合模式通用类图

从图中可以看到,主要有以下几个角色:

  • Component(根节点): 定义组合对象公用的方法或属性
  • Composite(树枝节点):组合树枝节点和树叶节点构造一个树形结构
  • Leaf(叶子节点):最小单元

启示:

提到组合模式,大家可能会觉得陌生,但是提到树形结构,大家可能就很熟悉,毕竟生活中到处都是“树”。那在Coding中,最常见的树形结构比如组织架构,菜单,权限设计,文件管理等等。那这次我们就用组织架构来谈谈组合模式的应用。一个公司是由部门组成的,部门下是员工,典型的组合模式。
就一般公司的组织架构而言,最上层为CEO,也就是根节点,往下就是各部门经理,每个部门经理可能负责有多个项目,每个项目都安排有对应的项目组长负责,那项目组就是由项目成员组成了。为了演示方便,我们的层级就不往下深入了。下面是我简化的组织架构图。

代码:

在我们的组织架构中我们主要分为三块,组织、部门、员工。
首先来看看Component角色,对应代码中的Organization。其中主要定义了公用的属性,职位、姓名及上级节点。上级节点的主要作用是用来从下往上遍历。

/// <summary>
///     组织架构
/// </summary>
public abstract class Organization
{
    /// <summary>
    ///     成员姓名
    /// </summary>
    public string MemberName { get; set; }

    /// <summary>
    ///     成员职位
    /// </summary>
    public string MemberPosition { get; set; }

    /// <summary>
    ///     直接上级
    /// </summary>
    public Organization ParentNode { get; set; }

    public void Display()
    {
        var basicInfo = string.Format("姓名:{0},职位:{1}", MemberName, MemberPosition);
        var parentInfo = ParentNode == null
            ? ""
            : string.Format(",直接上级:『姓名:{0},职位:{1}』", ParentNode.MemberName, ParentNode.MemberPosition);
        Console.WriteLine(basicInfo + parentInfo);
    }
}

再来看看对应Composite角色,部门的实现。在组织架构中,部门是组合的关键,也就是树枝节点。

/// <summary>
///     部门
/// </summary>
public class Department : Organization
{
    private readonly List<Organization> _organizationInfo = new List<Organization>();

    public Department(string departmentName, string charge)
    {
        MemberPosition = departmentName;
        MemberName = charge;
    }

    public void Add(Organization org)
    {
        _organizationInfo.Add(org);
        org.ParentNode = this;
    }

    public void Remove(Organization org)
    {
        _organizationInfo.Remove(org);
    }

    public List<Organization> GetDepartmentMembers()
    {
        return _organizationInfo;
    }
}

最后来看树叶节点,员工的实现。因为我们在组织节点未定义抽象方法,所以,直接继承自Organization即可。

/// <summary>
///     员工
/// </summary>
public class Member : Organization
{
}

来看看场景类:

internal class Program
{
    private static void Main(string[] args)
    {
        Console.WriteLine("组合模式:");
        Console.WriteLine("-------------------");

        var organzation = new Department("CEO", "老总");
        var developDepart = new Department("研发部经理", "研哥");

        organzation.Add(developDepart);

        var projectA = new Department("Erp项目组长", "E哥");

        developDepart.Add(projectA);

        var memberX = new Member {MemberPosition = "开发工程师", MemberName = "开发X"};
        var memberY = new Member {MemberPosition = "开发工程师", MemberName = "开发Y"};
        var memberZ = new Member {MemberPosition = "测试工程师", MemberName = "测试Z"};

        projectA.Add(memberX);
        projectA.Add(memberY);
        projectA.Add(memberZ);

        Console.WriteLine("组合模式:从上往下遍历");
        DisplayStructure(organzation);
        Console.WriteLine("-------------------");
        Console.WriteLine();

        Console.WriteLine("组合模式:从下往上遍历");
        FindParent(memberX);
        Console.WriteLine("-------------------");
        Console.ReadLine();
    }

    /// <summary>
    ///     正序排序
    /// </summary>
    /// <param name="org"></param>
    private static void DisplayStructure(Organization org)
    {
        if (org.ParentNode == null)
            org.Display();

        var departMent = (Department) org;

        foreach (var depart in departMent.GetDepartmentMembers())
        {
            depart.Display();
            if (!(depart is Member))
                DisplayStructure((Department) depart);
        }
    }

    /// <summary>
    ///     倒序排序
    /// </summary>
    /// <param name="member"></param>
    private static void FindParent(Organization member)
    {
        member.Display();
        while (member.ParentNode != null)
        {
            member.ParentNode.Display();
            member = member.ParentNode;
        }
    }
}

在场景类中,我们创建了组织架构图中左边的这条线,并提供了从上往下、从下往上两种遍历方法。输出结果如下:

遍历方式
输出结果

本实例主要用于简单演示组合模式的用法,还有很多需要完善的地方。小伙伴不妨想一想如果在我们真实的项目中该如何设计呢?

扩展

在组合模式中有一种透明组合模式,之所以透明,是因为将composite角色中也就是树叶节点的用来组合的方法挪到了component角色根节点抽象类中定义,再由树枝树叶单独实现。这样是将树枝和树叶的结构保存了一致,但引入了一个问题,因为树叶节点最小单元是不需要去组装的,但是又必须实现抽象类定义的组合方法,就只能给一个空实现throw new NotImplementedException(),处理不当就会抛出异常。
那相对来说,我们上例就是组合模式的安全实现。


总结:

组合模式的目的是简化部分整体的组装方式及遍历方式。高层模块(客户端)通过统一处理Composite角色,就可以完成部分整体的组装和拆解。根据自己的需求,在Component中定义公用的方法由树枝树叶实现,比如:通过定义设置获取上级节点的方法,即可完成从下往上遍历。

优缺点:

优点

  1. 扩展性好:树枝和树叶节点可以自由扩展,符合OCP。
  2. 方便高层调用:局部和整体对高层模块来说没有区别,树形机构中的所有节点都是Component。

缺点

  1. 不是面向接口编程,与依赖倒置原则相违背。

应用场景:

  1. 树形结构
  2. 部分整体关系
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容