组合模式(Composite)定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
UML类图如下:
比如《大话》中举的例子,公司总部在北京,然后在南京、杭州设有办事处,总公司和分支办事处都有相似的组织结构,比如都有人力资源部、财务部等。如下图:
再有HeadFirst举的关于菜单的例子,如下图:
如果程序需要表达上面例子中的公司层级组织或是菜单层级的结构,就可以采用组合模式。
组合模式能让我们用树形方式创建对象的结构,树里面包含了组合以及个别的对象。使用组合结构,我们能把相同得操作应用在组合和个别对象上。换句话说,在大对数大多数情况下,我们可以忽略对象组合和个别对象之间的差别。这为编程带了了极大的方便。
关于餐厅菜单的代码如下:
MenuComponent类:
1 class MenuComponent 2 { 3 public virtual void Add(MenuComponent menuComponent) 4 { 5 throw new NotImplementedException(); 6 } 7 public virtual void Remove(MenuComponent menuComponent) 8 { 9 throw new NotImplementedException(); 10 } 11 public virtual MenuComponent GetChild(int i) 12 { 13 throw new NotImplementedException(); 14 } 15 public virtual string GetName() 16 { 17 throw new NotImplementedException(); 18 } 19 public virtual string GetDescription() 20 { 21 throw new NotImplementedException(); 22 } 23 public virtual double GetPrice() 24 { 25 throw new NotImplementedException(); 26 } 27 public virtual bool IsVegetarian() 28 { 29 throw new NotImplementedException(); 30 } 31 public virtual void Print() 32 { 33 throw new NotImplementedException(); 34 } 35 }
所有组件都必须继承MenuComponent类,类中的虚方法都默认抛出异常,子类根据需要进行重写。
MenuItem类(比如午餐菜单中的某一道具体的菜):
1 class MenuItem : MenuComponent 2 { 3 string name; 4 string description; 5 bool isVegetarian; 6 double price; 7 8 public MenuItem(string name, string description, bool isVegetarian, double price) 9 { 10 this.name = name; 11 this.description = description; 12 this.isVegetarian = isVegetarian; 13 this.price = price; 14 } 15 16 public override string GetName() 17 { 18 return name; 19 } 20 public override string GetDescription() 21 { 22 return description; 23 } 24 public override double GetPrice() 25 { 26 return price; 27 } 28 public override bool IsVegetarian() 29 { 30 return isVegetarian; 31 } 32 public override void Print() 33 { 34 Console.Write("-----Name:{0}--Price:{1}--Description:{2}--{3}", name, price, description, (isVegetarian ? "V" : "")); 35 Console.WriteLine(); 36 } 37 }
Menu类(比如煎饼屋菜单、午餐菜单):
1 class Menu : MenuComponent 2 { 3 List<MenuComponent> listMenuComponent = new List<MenuComponent>(); 4 string name; 5 string description; 6 7 public Menu(string name, string description) 8 { 9 this.name = name; 10 this.description = description; 11 } 12 13 public override void Add(MenuComponent menuComponent) 14 { 15 listMenuComponent.Add(menuComponent); 16 } 17 public override void Remove(MenuComponent menuComponent) 18 { 19 listMenuComponent.Remove(menuComponent); 20 } 21 public override MenuComponent GetChild(int i) 22 { 23 return listMenuComponent[i]; 24 } 25 public override string GetName() 26 { 27 return name; 28 } 29 public override string GetDescription() 30 { 31 return description; 32 } 33 public override void Print() 34 { 35 Console.WriteLine("--Name:{0}--Description:{1}", name, description); 36 foreach (var item in listMenuComponent) 37 { 38 item.Print(); 39 } 40 } 41 }
Menu类不必重写GetPrice()和IsVegetarian()方法,而且Menu类的Print()类比较特殊,需要遍历内部的所有MenuItem类并逐个打印。