温故知新(5)——组合模式
概述
组合模式大概是每个人都用过的一种模式,或有心,或无意。因为如果要把一个一个的节点组合成“树”,组合模式的写法应该是比较自然的一种表达。但是个体与整体的访问一致性,可能需要特别注意一下。先看看GOF给出的模式意图。
将对象组合成树形结构以表示“部分-整体”的层次结构,使用户对单个对象和组合对象的使用具有一致性。
也是就是说:
1、组合模式用来构建“部分-整体”的层次结构,也就是树;
2、客户端对象使用对象个体和对象的组合体时,方法相同。
组合模式的实现过程中有一些值得考虑的地方,下节将详述。
结构
下面是组合模式的类图:
整理一下模式参与者(为了表述清楚,将使用树形结构的术语):
1、树节点的抽象,叶节点和非叶节点都要实现这个接口——IComposite;
2、叶节点,组合中最基础的单位——Leaf;
3、分支节点,可以包含其他分支节点或叶节点——Composite;
4、客户端代码——Client。
上图的IComposite的接口中,除定义了Operation这样一个公共的操作以外,还定义了Add、Remove的子对象的管理方法,这样当Leaf类实现这个接口时就必须实现这些对它来说没有意义的方法(因为叶子不包含子对象)。这虽然有些违反“类只能定义对它子类有意义的操作”这条设计原则,但是换来的是Client对Leaf和Composite使用的一致性。我们可以将上面的做法称为接口最大化,相反也可以采用接口最小化的做法,即IComposite只包含Operation方法,而将子对象的管理方法放入Composite中实现,这样Leaf将变得清晰,但是Client在使用Leaf和Composite就要区别对待。由于GOF的意图中强调了这种使用的一致性,因此本文将采用接口最大化的方法。但在实际使用中应该具体分析,在两种方法中做出取舍。
示例
金融研究机构经常针对当前的经济形势撰写研究报告,为了更好的管理这些报告,需要对这些报告进行分类。分类不仅限于一级,即分类下可以包含子分类,子分类下可以再包含子分类,由此构成了一个分类树,研究报告包含在叶子节点的分类中。需求要求选择任意一个分类节点(跟、分支、叶子),列出这个分类下所有的报告。下面的实现在把叶子节点称为分类Category,分支节点成为分类组。
1、定义分类的接口ICategory。
1: using System;
2: using System.Collections.Generic;
3:
4: namespace DesignPatterns.Composite
5: {
6: /// <summary>
7: /// 报告分类接口
8: /// </summary>
9: public interface ICategory
10: {
11: //分类名称
12: string Name { get; set; }
13:
14: //获取分类下的报告
15: List<Report> GetReports();
16:
17: //添加子分类
18: void AddSubCategory(ICategory category);
19: //移除子分类
20: void RemoveSubCategory(ICategory category);
21: //获取子分类
22: ICategory GetSubCategory(int index);
23: //子分类个数
24: int Count { get; }
25: }
26: }
27:
2、实现分类Category。可以看到Category空实现了子分类的管理方法。
1: using System;
2: using System.Collections.Generic;
3:
4: namespace DesignPatterns.Composite
5: {
6: /// <summary>
7: /// 具体分类
8: /// </summary>
9: public class Category : ICategory
10: {
11: public Category(string name)
12: {
13: this.Name = name;
14: }
15:
16: public string Name { get; set; }
17:
18: public List<Report> GetReports()
19: {
20: return new List<Report>() { new Report(), new Report(), new Report() };
21: }
22:
23: public void AddSubCategory(ICategory category)
24: {
25: throw new NotSupportedException("具体分类不支持此操作。");
26: }
27:
28: public void RemoveSubCategory(ICategory category)
29: {
30: throw new NotSupportedException("具体分类不支持此操作。");
31: }
32:
33: public ICategory GetSubCategory(int index)
34: {
35: throw new NotSupportedException("具体分类不支持此操作。");
36: }
37:
38: public int Count
39: {
40: get { throw new NotSupportedException("具体分类不支持此操作。"); }
41: }
42: }
43: }
44:
3、实现分类组CategoryGroup。
1: using System;
2: using System.Collections.Generic;
3:
4: namespace DesignPatterns.Composite
5: {
6: /// <summary>
7: /// 分类组
8: /// </summary>
9: public class CategoryGroup : ICategory
10: {
11: public CategoryGroup(string name)
12: {
13: this.Name = name;
14: this.subCategories = new List<ICategory>();
15: }
16:
17: public string Name { get; set; }
18:
19: public List<Report> GetReports()
20: {
21: List<Report> reports = new List<Report>();
22: foreach (var c in this.subCategories)
23: {
24: reports.AddRange(c.GetReports());
25: }
26: return reports;
27: }
28:
29: private List<ICategory> subCategories;
30:
31: public void AddSubCategory(ICategory category)
32: {
33: this.subCategories.Add(category);
34: }
35:
36: public void RemoveSubCategory(ICategory category)
37: {
38: this.subCategories.Remove(category);
39: }
40:
41: public ICategory GetSubCategory(int index)
42: {
43: return this.subCategories[index];
44: }
45:
46: public int Count
47: {
48: get
49: {
50: return this.subCategories.Count;
51: }
52: }
53: }
54: }
55:
4、添加一个表示报告的实体类Report。
1: using System;
2:
3: namespace DesignPatterns.Composite
4: {
5: /// <summary>
6: /// 报告实体类
7: /// </summary>
8: public class Report
9: {
10: public Report()
11: {
12: //每次生成GUID对象的hash码做种子,生成随机数
13: Random r = new Random(Guid.NewGuid().GetHashCode());
14: this.Id = r.Next(1, 999999);
15: }
16:
17: //报告ID
18: public int Id { get; set; }
19: }
20: }
21:
5、客户端代码。
1: using System;
2:
3: namespace DesignPatterns.Composite
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: //组合过程
10: ICategory root = new CategoryGroup("全部报告");
11: ICategory macro = new Category("宏观研究");
12: ICategory industry = new CategoryGroup("行业研究");
13: ICategory agriculture = new Category("农业");
14: ICategory finance = new Category("金融业");
15: industry.AddSubCategory(agriculture);
16: industry.AddSubCategory(finance);
17: root.AddSubCategory(macro);
18: root.AddSubCategory(industry);
19:
20: //根节点、分支节点、叶节点的使用完成一致。
21: DisplayReports(root);
22: DisplayReports(industry);
23: DisplayReports(agriculture);
24:
25: Console.WriteLine("按任意键结束...");
26: Console.ReadKey();
27: }
28:
29: //输出
30: private static void DisplayReports(ICategory category)
31: {
32: Console.WriteLine(category.Name + ":");
33: foreach (var r in category.GetReports())
34: {
35: Console.WriteLine(r.Id);
36: }
37: Console.WriteLine("===============================");
38: }
39: }
40: }
41:
6、运行,查询结果。
博文(http://blog.csdn.net/ai92/article/details/298336),中有一个取自开源测试框架JUnit中的例子,可以参考。