部分整体,树形结构,『组合模式』来帮忙
定义:(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中定义公用的方法由树枝树叶实现,比如:通过定义设置获取上级节点的方法,即可完成从下往上遍历。
优缺点:
优点
- 扩展性好:树枝和树叶节点可以自由扩展,符合OCP。
- 方便高层调用:局部和整体对高层模块来说没有区别,树形机构中的所有节点都是Component。
缺点
- 不是面向接口编程,与依赖倒置原则相违背。
应用场景:
- 树形结构
- 部分整体关系