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

目录:设计模式之小试牛刀
源码路径: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. 部分整体关系
    原文作者:圣杰
    原文地址: https://www.jianshu.com/p/9e49e5d702df
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞