场景:
如果想模拟windows的开始菜单,分析一下会发现里面的菜单项:有些有子菜单,有些则没有;因此大体可以将菜单类分为二类,设计代码如下:
/// <summary> /// 菜单的显示接口 /// </summary> public interface IMenu { void Show(); } /// <summary> /// 菜单基类 /// </summary> public class MenuBase { public string MenuName { set; get; } } /// <summary> /// 有子菜单的菜单类 /// </summary> public class Menu : MenuBase, IMenu { public void Show() { Console.WriteLine(MenuName); } private IList<IMenu> _children; public IList<IMenu> Children { get { if (_children == null) { _children = new List<IMenu>(); } return _children; } } } /// <summary> /// 无子菜单的菜单类(即最底级菜单) /// </summary> public class SingleMenu : MenuBase, IMenu { public void Show() { Console.WriteLine( MenuName); } }
客户端示例调用如下:
class Program { //客户程序 static void Main(string[] args) { Menu menuContainer = new Menu() { MenuName = "开始" }; SingleMenu menuShutdown = new SingleMenu() { MenuName = "\t关机" }; Menu menuProgram = new Menu() { MenuName = "\t程序" }; SingleMenu menuStartUp = new SingleMenu() { MenuName = "\t\t启动" }; menuProgram.Children.Add(menuStartUp); menuContainer.Children.Add(menuProgram); menuContainer.Children.Add(menuShutdown); menuContainer.Show(); foreach (IMenu item in menuContainer.Children) { if (item is SingleMenu) { item.Show(); } else if (item is Menu) { ShowAllSubMenu(item); } } Console.ReadLine(); } /// <summary> /// 客户端调用的递归Show程序 /// </summary> /// <param name="menu"></param> static void ShowAllSubMenu(IMenu menu) { if (menu is SingleMenu) { menu.Show(); } else if (menu is Menu) { menu.Show(); IList<IMenu> children = (menu as Menu).Children; foreach (IMenu i in children) { ShowAllSubMenu(i); } } } }
从功能正确性上讲,上面的示意代码并无大错,但是如果从客户程序上考虑,却发现这样并非最佳实践:客户程序依赖了太多的Menu类细节,客户程序在树型菜单创建完成后,最关心的莫过于如何把菜单完整的显示出来,但上面的代码中为了达到这个目的,却不得不知道子菜单的内部实现(通过Children和类型判断),如果以后菜单类升级,修改了内部构造(比如将Children改成GetChildren),客户程序将被迫重新修改,这时候组合(Composite)模式就派上用场了。
using System; using System.Collections.Generic; namespace composite { class Program { //客户程序 static void Main(string[] args) { IMenu menuContainer = new Menu() { MenuName = "开始" }; IMenu menuShutdown = new SingleMenu() { MenuName = "\t关机" }; IMenu menuProgram = new Menu() { MenuName = "\t程序" }; IMenu menuStartUp = new SingleMenu() { MenuName = "\t\t启动" }; menuProgram.Add(menuStartUp); menuContainer.Add(menuProgram); menuContainer.Add(menuShutdown); menuContainer.Show(); Console.ReadLine(); } } /// <summary> /// 菜单的显示接口 /// </summary> public interface IMenu { void Show(); void Add(IMenu menu); void Remove(IMenu menu); } /// <summary> /// 菜单基类 /// </summary> public abstract class MenuBase { public string MenuName { set; get; } protected IList<IMenu> Children; } /// <summary> /// 有子菜单的菜单类 /// </summary> public class Menu : MenuBase,IMenu { public void Show() { ShowAllSubMenu(this); } public void ShowAllSubMenu(IMenu menu) { if (menu is SingleMenu) { menu.Show(); } else if (menu is Menu) { Console.WriteLine((menu as Menu).MenuName); IList<IMenu> children = (menu as Menu).Children; foreach (IMenu i in children) { ShowAllSubMenu(i); } } } public void Add(IMenu menu) { if (Children == null) { Children = new List<IMenu>(); } Children.Add(menu); } public void Remove(IMenu menu) { if (Children == null) { Children = new List<IMenu>(); } Children.Add(menu); } } /// <summary> /// 无子菜单的菜单类(即最底级菜单) /// </summary> public class SingleMenu :MenuBase, IMenu { public void Show() { Console.WriteLine( MenuName); } public void Add(IMenu menu) { throw new Exception("最底层菜单无法Add子元素!"); } public void Remove(IMenu menu) { throw new Exception("最底层菜单无法Remove子元素!"); } } }
最后再来理解Composite的意图可能就更清楚了(摘自terrylee的“.NET设计模式(11):组合模式(Composite Pattern)"):
概述
组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解 耦。
意图
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。[GOF 《设计模式》]
类图: