静悄悄中时间飞逝,感叹一句最近TMD真忙真累,好在天生坚强性格阳光,不吹不黑,下面开始讲结构型模式(Structural patterns)的第2篇,Composite Pattern,中文称之为组合或复合模式。
定义:
Composite模式,能够灵活地实现循环、递归、部分-整体类的结构,使外部世界能够以一致的方式处理单体或集合。
解决的问题:
- 就像定义中所提及的那样,如何使客户端能够以一致的方式去访问某个单体或者集合,以使结构更加简洁
- 如何优雅的表达一个树形结构
怎么去解决(包含叶子Leaf的实现方式):
- 通过定义一个接口代表集合与个体所拥有的相同属性与行为,如IComponent
- 通过定义一个类Composite,它继承自IComponent,同时通过组合的方式它拥有一个名称为Components的属性,它代表着一个IComponent集合
- Composite有能力将一个实现了IComponent接口的对象加入到Components中
- 对于Composite的任何请求,都会被重定向到属性Components中的每个个体分别去执行
- 通过定义一个类Leaf,它继承自IComponent,代表着不可再分割的个体
举个栗子,在一个树状结构中,树杆、树枝、树叉皆由Composite来表示,而Leaf代表的是末端的每片树叶。
怎么去解决(进一步抽象):
通过分析我们能够发现在上面的解决方式中,如果有能力分辨一个IComponent是树杆还是树叶,便可以将Composite和Leaf合并为一个类型,于是我们为IComponent引入一个新的属性HasChild,并且给它换一个更贴切的名字INode,如果HasChild为True,则代表它是Composite,反之则代表它是Leaf
- 通过定义一个接口代表集合与个体所拥有的相同属性与行为,如INode,该接口拥有一个称为HasChild的布尔属性
- 通过定义一个类Node,它继承自INode,同时通过组合的方式它拥有一个名称为Nodes的属性,它代表着一个INode集合
- Node有能力添加一个实现了INode接口的对象到Nodes中
- 对于Node的任何请求,如果HasChild为True,那么请求会被重定向到属性Nodes的每一个个体分别去执行,如果HasChild为False,那么请求将由自身进行处理
UML:
实例:
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace CompositePattern { internal class Program { private static void Main(string[] args) { var orginazation = new Node() { Name = "Microsoft" }; var ceoOffice = new Node() { Name = "CEO Office" }; var ceo = new Node() { Name = "CEO" }; var cloudDepartment = new Node() { Name = "Cloud Department" }; var departmentManager = new Node() { Name = "Department Manager" }; var projectGroup = new Node() { Name = "Project Group" }; var projectManager = new Node() { Name = "Project Manager" }; var foobar = new Node() { Name = "Foobar" }; var you = new Node { Name = "You" }; orginazation.Add(ceoOffice); ceoOffice.Add(ceo); ceoOffice.Add(cloudDepartment); cloudDepartment.Add(departmentManager); cloudDepartment.Add(projectGroup); projectGroup.Add(projectManager); projectGroup.Add(foobar); projectGroup.Add(you); Console.WriteLine(orginazation.GetHierarchy()); Console.ReadKey(); } } /// <summary> /// Node /// </summary> public interface INode { string Name { get; set; } /// <summary> /// Adds the specified node. /// </summary> /// <param name="node">The node.</param> void Add(INode node); /// <summary> /// Gets the hierarchy. /// </summary> /// <returns></returns> string GetHierarchy(); /// <summary> /// Gets a value indicating whether this instance has child node. /// </summary> /// <value> /// <c>true</c> if this instance has child; otherwise, <c>false</c>. /// </value> bool HasChild { get; } /// <summary> /// Gets the child nodes. /// </summary> /// <value> /// The nodes. /// </value> ICollection<INode> Nodes { get; } /// <summary> /// Gets or sets the parent. /// </summary> /// <value> /// The parent. /// </value> INode Parent { get; set; } } public class Node : INode { public bool HasChild { get { return Nodes.Any(); } } public string Name { get; set; } public ICollection<INode> Nodes { get; } = new Collection<INode>(); public INode _parent; public INode Parent { get { return _parent; } set { if (_parent != null) throw new Exception("The parent only can be set up once."); _parent = value; } } public void Add(INode node) { Nodes.Add(node); node.Parent = this; } private int GetFloorNumber(INode node, int floors = 0) { if (node != null) { floors++; return GetFloorNumber(node.Parent, floors); } return floors; } public string GetHierarchy() { var indents = string.Empty; var floorNumber = GetFloorNumber(this); for (int i = 0; i < floorNumber; i++) { indents = $">{indents}"; } var result = $"{indents} {Name}"; if (HasChild) { foreach (var item in Nodes) { var childName = item.GetHierarchy(); result = $"{result}{Environment.NewLine}{childName}"; } } return $"{result}"; } } }
注释:
输出: