温故知新(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、运行,查询结果。

image

博文(http://blog.csdn.net/ai92/article/details/298336),中有一个取自开源测试框架JUnit中的例子,可以参考。

posted @ 2012-09-07 16:54  宽厚  阅读(1398)  评论(2编辑  收藏  举报