一般情况下,组件可以是单独的一个对象,也可以是对象的组合,组合模式就是为了迎合这两种情况进行设计。组合可以被用来构建部分-整体层次结构,或者是构造树形的数据表示方式。组合是对象的集合,而其中的任何一个对象又可能是一个组合,或者是一个简单的对象。
在树形结构中,访问组合中所有的对象要求有一个简单的单一访问接口,但同时要求能够区分开节点和叶子。在构造组合的时候,我们需要决定哪个节点是元素哪个是叶子。在这里我们可以通过子节点个数进行判断是不是叶子节点。
比如构造一个公司的人事管理系统,经理下面有部门经理,部门经理下面有小组负责人,小组负责人下面有员工这样一个树形结构。在程序里我们需要显示每个人的成本,对于普通员工,一个人的成本就是他的工资,而对于部门经理,他的成本就是他的工资加上手下所有人的工资。
我们需要定义两种类:雇员类和老板类,雇员类是叶子节点,老板类下面可以包含子节点。
用户界面如图:
一个类共同的接口:
using System;
using System.Collections ;
namespace Composite
{
/// <summary>
/// Summary description for AbstractEmployee.
/// </summary>
public interface AbstractEmployee {
float getSalary(); //get current salary
string getName(); //get name
bool isLeaf(); //true if leaf
void add(string nm, float salary); //add subordinate
void add(AbstractEmployee emp); //add subordinate
IEnumerator getSubordinates(); //get subordinates
AbstractEmployee getChild(); //get child
float getSalaries(); //get salaries of all
}
}
雇员类的定义:
using System;
using System.Collections ;
namespace Composite
{
/// <summary>
/// Summary description for Employee.
/// </summary>
public class Employee :AbstractEmployee {
protected float salary;
protected string name;
protected ArrayList subordinates;
//------
public Employee(string nm, float salry) {
subordinates = new ArrayList();
name = nm;
salary = salry;
}
//------
public float getSalary() {
return salary;
}
//------
public string getName() {
return name;
}
//------
public bool isLeaf() {
return subordinates.Count == 0;
}
//------
public virtual void add(string nm, float salary) {
throw new Exception("No subordinates in base employee class");
}
//------
public virtual void add(AbstractEmployee emp) {
throw new Exception("No subordinates in base employee class");
}
//------
public IEnumerator getSubordinates() {
return subordinates.GetEnumerator ();
}
public virtual AbstractEmployee getChild() {
return null;
}
//------
public float getSalaries() {
float sum;
AbstractEmployee esub;
//get the salaries of the boss and subordinates
sum = getSalary();
IEnumerator enumSub = subordinates.GetEnumerator() ;
while (enumSub.MoveNext()) {
esub = (AbstractEmployee)enumSub.Current;
sum += esub.getSalaries();
}
return sum;
}
}
}
这里通过IEnumerator对这个节点的子节点进行遍历。
getSalaries() 函数是一个递归的调用,可以计算自身工资和所有子节点工资的总和。
Boss类的定义如下:
using System;
using System.Collections ;
namespace Composite
{
/// <summary>
/// Summary description for Boss.
/// </summary>
public class Boss:Employee
{
public Boss(string name, float salary):base(name,salary) {
}
//------
public Boss(AbstractEmployee emp):base(emp.getName() , emp.getSalary()) {
}
//------
public override void add(string nm, float salary) {
AbstractEmployee emp = new Employee(nm,salary);
subordinates.Add (emp);
}
//------
public override void add(AbstractEmployee emp){
subordinates.Add(emp);
}
//------
public override AbstractEmployee getChild() {
bool found;
AbstractEmployee tEmp = null;
IEnumerator esub ;
if (getName().Equals (getName()))
return this;
else {
found = false;
esub = subordinates.GetEnumerator ();
while (! found && esub.MoveNext()) {
tEmp = (AbstractEmployee)esub.Current;
found = (tEmp.getName().Equals(name));
if (! found) {
if (! tEmp.isLeaf()) {
tEmp = tEmp.getChild();
found = (tEmp.getName().Equals(name));
}
}
}
if (found)
return tEmp;
else
return new Employee("New person", 0);
}
}
}
}
Boss类继承了Employee类,因为Boss类本身也是雇员。
在主程序中就可以构建雇员的树形结构了:
private void buildEmployeeList() {
prez = new Boss("CEO", 200000);
marketVP = new Boss("Marketing VP", 100000);
prez.add(marketVP);
salesMgr = new Boss("Sales Mgr", 50000);
advMgr = new Boss("Advt Mgr", 50000);
marketVP.add(salesMgr);
marketVP.add(advMgr);
prodVP = new Boss("Production VP", 100000);
prez.add(prodVP);
advMgr.add("Secy", 20000);
//add salesmen reporting to sales manager
for (int i = 1; i<=5; i++){
salesMgr.add("Sales" + i.ToString(), rand_sal(30000));
}
prodMgr = new Boss("Prod Mgr", 40000);
shipMgr = new Boss("Ship Mgr", 35000);
prodVP.add(prodMgr);
prodVP.add(shipMgr);
for (int i = 1; i<=3; i++){
shipMgr.add("Ship" + i.ToString(), rand_sal(25000));
}
for (int i = 1; i<=4; i++){
prodMgr.add("Manuf" + i.ToString(), rand_sal(20000));
}
}
//-----
private void buildTree() {
EmpNode nod;
nod = new EmpNode(prez);
rootNode = nod;
EmpTree.Nodes.Add(nod);
addNodes(nod, prez);
}
using System;
using System.Windows.Forms;
namespace Composite
{
/// <summary>
/// Summary description for EmpNode.
/// </summary>
public class EmpNode:TreeNode {
private AbstractEmployee emp;
public EmpNode(AbstractEmployee aemp ):base(aemp.getName ()) {
emp = aemp;
}
//-----
public AbstractEmployee getEmployee() {
return emp;
}
}
}
这样就完成一个组合模式的对对象的封装。
然后还需要下面一些时间函数:
private void EmpTree_AfterSelect(object sender, TreeViewEventArgs e) {
EmpNode node;
node = (EmpNode)EmpTree.SelectedNode;
getNodeSum(node);
}
private void getNodeSum(EmpNode node) {
AbstractEmployee emp;
float sum;
emp = node.getEmployee();
sum = emp.getSalaries();
lbSalary.Text = sum.ToString ();
在这个例子的基础上,我们还可以添加树形结构的双向链表,从而可以进行从叶子节点向上遍历,方便的查找父类信息。
这样对Employee类的改动如下:
using System;
using System.Collections ;
namespace Composite
{
/// <summary>
/// Summary description for Employee.
/// </summary>
public class Employee :AbstractEmployee {
protected float salary;
protected string name;
protected AbstractEmployee parent;
protected ArrayList subordinates;
//------
public Employee(AbstractEmployee parnt, string nm, float salry) {
subordinates = new ArrayList();
name = nm;
salary = salry;
parent = parnt;
}
//------
public AbstractEmployee getBoss() {
return parent;
}
//------
public float getSalary() {
return salary;
}
//------
public string getName() {
return name;
}
//------
public bool isLeaf() {
return subordinates.Count == 0;
}
//------
public virtual void add(string nm, float salary) {
throw new Exception("No subordinates in base employee class");
}
//------
public virtual void add(AbstractEmployee emp) {
throw new Exception("No subordinates in base employee class");
}
//------
public IEnumerator getSubordinates() {
return subordinates.GetEnumerator ();
}
public virtual AbstractEmployee getChild() {
return null;
}
//------
public float getSalaries() {
float sum;
AbstractEmployee esub;
//get the salaries of the boss and subordinates
sum = getSalary();
IEnumerator enumSub = subordinates.GetEnumerator() ;
while (enumSub.MoveNext()) {
esub = (AbstractEmployee)enumSub.Current;
sum += esub.getSalaries();
}
return sum;
}
}
}
这个类中添加了一个指向父类的引用。
这样就可以向上遍历,找到上级领导信息:
private void btShowBoss_Click(object sender, System.EventArgs e) {
EmpNode node;
node = (EmpNode)EmpTree.SelectedNode;
AbstractEmployee emp = node.getEmployee ();
string bosses = "";
while(emp != null) {
bosses += emp.getName () +"\n";
emp = emp.getBoss();
}
MessageBox.Show (null, bosses,"Reporting chain");
}
组合模式允许定义简单对象和更加复杂的组件对象的类层次结构,这样这些对象就能够以相同的方式呈献给客户端程序。基于这一简易性,客户端就可以更加价单,因为节点和叶子可以被以同样的方式处理。
组合模式还可以让你很容易的添加新类型的组件到集合中,只要这些组件支持相类似的编程接口就可以了。