.NET实用设计模式:组合模式(Composite)

组合模式(Composite)是一种“结构型”模式(Structural)。结构型模式涉及的对象为两个或两个以上,表示对象之间的活动,与对象的结构有关。

先举一个组合模式的小小例子:




如图:系统中有两种Box:Game Box和Internet Box,客户需要了解者两个类的接口分别进行调用。为了简化客户的工作,创建了XBox类,程序代码如下:

GameBox的代码:

public class GameBox 

    
public void PlayGame() 
    

        Console.WriteLine(
"plaly game"); 
    }
 
}
 

InternetBox的代码:

public class InternetBox 

    
public void ConnectToInternet() 
    
{           
        Console.WriteLine(
"connect to internet"); 
    }
 
    
public void GetMail() 
    
{           
        Console.WriteLine(
"check email"); 
    }
 
}
 

XBox的代码:

public class XBox 

    
private GameBox mGameBox=null
    
private InternetBox mInternetBox=null
    
public XBox() 
    

        mGameBox 
= new GameBox(); 
        mInternetBox 
= new InternetBox(); 
    }
 
    
public void PlayGame() 
    

        mGameBox.PlayGame(); 
    }
 
    
public void ConnectToInternet() 
    

        mInternetBox.ConnectToInternet(); 
    }
 
    
public void GetMail() 
    

        mInternetBox.GetMail(); 
    }
       
}

XBox中封装了GameBox和InternetBox的方法,这样,用户面对的情况就大大的简化了,调用的代码如下:

public class CSComposite 

     
static void Main (string[] args) 
     

         XBox x 
= new XBox(); 
         Console.WriteLine(
"PlayGame!"); 
         x.PlayGame(); 
         Console.WriteLine(); 
         Console.WriteLine(
"Internet Play Game!"); 
         x.ConnectToInternet(); 
         x.PlayGame(); 
         Console.WriteLine(); 
         Console.WriteLine(
"E-Mail!"); 
         x.GetMail(); 
     }
       
}


可以看见,用户只需要了解XBox的接口就可以了。

组合模式的应用例子

组合模式适用于下面这样的情况:两个或者多个类有相似的形式,或者共同代表某个完整的概念,外界的用户也希望他们合而为一,就可以把这几个类“组合”起来,成为一个新的类,用户只需要调用这个新的类就可以了。

下面举一个例子说明Composite模式的一个实际应用。下面的Class视图:



Employee类是AbstractEmployee接口的一个实现,Boss类是Employee的一个子类,EmpNode是从树视图的TreeNode类继承而来的。我们先看看代码:

AbstractEmployee,这是一个接口,提供下列方法:

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
}


Employee类是AbstractEmployee接口的一个实现

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;
    }

}


从Employee接口和他的一个实现来看,下面很可能要将这个类型的数据组合成一个树的结构。

Boss类是Employee类的派生,他重载了Employee类的add和getChild方法:

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);
        }

    }

}


getChild方法是一个递归调用,如果Child不是Leaf,就继续调用下去。上面几个类表达了一个树的结构,表示出了公司中的领导和雇员的级别关系。

现在我们看一下这个程序需要达到的目标,程序运行后显示下面的界面:



界面上有一个树图,树上显示某公司的人员组织结构,点击这些雇员,会在下面出现这个人的工资。现在程序中有两棵树:一棵是画面上实际的树,另一个是公司中雇员的虚拟的树。画面上的树节点是TreeNode类型,雇员的虚拟树节点是AbstractEmployee类型。我们可以采用组合模式,创造一种新的“节点”,组合这两种节点的特性,简化窗体类需要处理的情况,请看下面的代码:

public class EmpNode:TreeNode {
    
private AbstractEmployee emp;

    
public EmpNode(AbstractEmployee aemp ):base(aemp.getName ()) {
        emp 
= aemp;
    }


    
//-----
    public AbstractEmployee getEmployee() {
        
return emp;
    }

}


EmpNode类是TreeNode类的子类,他具有TreeNode类的所有特性,同时他也组合了AbstractEmployee类型的特点。这样以来调用者的工作就简化了。下面是Form类的代码片断,我把自动生成的代码省略了一部分:

public class Form1 : System.Windows.Forms.Form {
    
private System.Windows.Forms.Label lbSalary;

    
/// <summary>
    
/// Required designer variable.
    
/// </summary>

    private System.ComponentModel.Container components = null;
    AbstractEmployee prez, marketVP, salesMgr;
    TreeNode rootNode;
    AbstractEmployee advMgr, emp, prodVP, prodMgr, shipMgr;
    
private System.Windows.Forms.TreeView EmpTree;
    
private Random rand;

    
private void init() {
        rand 
= new Random ();
        buildEmployeeList();
        buildTree();
    }


    
//---------------
    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);
    }


    
//------
    private void getNodeSum(EmpNode node) {
        AbstractEmployee emp;
        
float sum;
        emp 
= node.getEmployee();
        sum 
= emp.getSalaries();
        lbSalary.Text 
= sum.ToString ();
    }


    
//------
    private void addNodes(EmpNode nod, AbstractEmployee emp) {
        AbstractEmployee newEmp;
        EmpNode newNode;
        IEnumerator empEnum;
        empEnum 
= emp.getSubordinates();

        
while (empEnum.MoveNext()) {
            newEmp 
= (AbstractEmployee)empEnum.Current;
            newNode 
= new EmpNode(newEmp);
            nod.Nodes.Add(newNode);
            addNodes(newNode, newEmp);
        }

    }


    
//------
    private float rand_sal(float sal) {
        
float rnum = rand.Next ();
        rnum 
= rnum / Int32.MaxValue;
        
return rnum * sal / 5 + sal;
    }


    
//------
    public Form1() {
        
//
        
// Required for Windows Form Designer support
        
//
        InitializeComponent();
        init();
        
//
        
// TODO: Add any constructor code after InitializeComponent call
        
//
    }


    
/// <summary>
    
/// Clean up any resources being used.
    
/// </summary>

    protected override void Dispose( bool disposing ) {
        
if( disposing ) {
            
if (components != null{
                components.Dispose();
            }

        }

        
base.Dispose( disposing );
    }


    
/// <summary>
    
/// The main entry point for the application.
    
/// </summary>

    [STAThread]
    
static void Main() {
        Application.Run(
new Form1());
    }


    
private void EmpTree_AfterSelect(object sender, TreeViewEventArgs e) {
        EmpNode node;
        node 
= (EmpNode)EmpTree.SelectedNode;
        getNodeSum(node);
    }

}


EmpTree_AfterSelect方法是树图点击节点事件的响应方法,用户点击节点后在文本栏里显示相应的工资。组合模式已经介绍完了,下面的东西和组合模式没有什么关系。

为什么用interface

为什么要在程序中创建雇员的interface呢?我们可以创建一个class Employee,再派生出Boss,一样可以实现上面的功能嘛。

使用interface是为了将画面上的显示程序与后台的业务数据程序分离开。画面的显示程序只需要关心“雇员”提供哪些接口就可以工作了,而不去过问具体的细节,比如工资的计算规则。如果需要对界面类和数据类分别进行单元测试,这样的做法也提供了可能(也就是说,这个程序是可测试的)。测试画面的时候可以在雇员接口上实现一些虚假的雇员类,其中的方法和属性都是为了测试而假造的,这样就可以测试界面的显示是否正确。一般说来程序如果要进行单元测试,应该从设计阶段就考虑程序的“可测试性”,其中重要的一点是:将界面表示与业务逻辑分离开。


posted on 2004-11-29 13:04  小陆  阅读(2557)  评论(6编辑  收藏  举报