设计模式-组合模式

本篇文章来自于设计模式一书中的“组合模式”

    本篇中我们学习如何使用组合模式,通常在程序员开发的系统中,组件即可以是单个的对象,也可以是对象的集合。组合模式包括了这两种情况,组合就是对象的集合,其中的每个对象即可以是一个组合,也可以是简单的对象。在树的术语中,对象可以是带有其他分支的节点,也可以是叶子。

    开发方面存在的问题是,对组合中的所有对象都要求具有一个简单、单一的访问接口并要求能够区分节点与叶子,这二者是相互矛盾的。节点可以有孩子并允许添加孩子,而叶子节点不允许有孩子,在某些实现中要防止对它们添加孩子节点。

    我们考虑一个实际的需求,一个小公司,初期只有一个人,他当然就是CEO,尽管在初期他过于繁忙,不会考虑到这一点,接下来,他雇佣了两个人来分别管理销售和生产,很快这两个人又分别雇佣了另外一些助手,帮助做广告,运输等业务,这两个人于是成为公司的两位副总经理。随着公司的日益兴旺,公司人员持续增长,最后形成如下图所示的组织成员图。

计算薪水

    如果公司盈利,公司中的每个成员都会得到一份薪水,在任何时候都会要求计算从每个员工到整个公司的控制成本。将控制成本定义为员工及其所有下属的薪水。这是一个理想的组合例子。
每个雇员的成本就是他的薪水。
领导一个部门的雇员的成本是他的薪水加上其下属的薪水。
我们希望用一个简单的接口就能正确地生成薪水总和,不管雇员是否有下属。
float GetSalaries();

Employee类

    我们将公司设想为由节点构成的一个组合。使用一个类表示所有的员工是可以的,但由于每个层次的雇员有不同的属性,所有至少定义两个类(Employee类和Boss类)会更有效。Employee是叶子,他们下面没有雇员,Boss是节点,他们下面可以有雇员节点。我们先创建IEmployee类,然后从中派生出具体的Employee类。

    public interface IEmployee
    {
        /// <summary>
        /// 获取薪水
        /// </summary>
        /// <returns></returns>
        float GetSalary();

        /// <summary>
        /// 获取名字
        /// </summary>
        /// <returns></returns>
        string GetName();

        /// <summary>
        /// 是否是叶节点
        /// </summary>
        bool IsLeaf();

        /// <summary>
        /// add subordinate 添加下属
        /// </summary>
        /// <param name="name"></param>
        /// <param name="salary"></param>
        void Add(string name, float salary);

        /// <summary>
        /// 添加下属
        /// </summary>
        /// <param name="emp"></param>
        void Add(IEmployee emp);

        /// <summary>
        /// 获取当前节点下的所有下属
        /// </summary>
        /// <returns></returns>
        IEnumerator GetSubordinate();

        /// <summary>
        /// 获取员工
        /// </summary>
        /// <returns></returns>
        IEmployee GetChild();

        /// <summary>
        /// 从当前节点算起的薪水总和
        /// </summary>
        /// <returns></returns>
        float GetSalaries();
    }
IEmployee

    Employee类必须有add,remove,getChild和subordinates等方法的具体实现过程。由于Employee是叶子,所以,这些方法都要返回某种错误提示。GetSubordinate方法可以返回一个空值,但是,如果GetSubordinate方法返回一个空的枚举量,会使用程序更具有一致性。

public IEnumerator GetSubordinate()
{
    return Subordinates.GetEnumerator();
}

    由于Employee类的成员不能有下属,它的add和remove方法就必须产生错误提示。在调用Employee类的这些方法时就让它们抛出一个异常。

public virtual void Add(string name, float salary)
{
    throw new Exception("这个是普通员工不能有下属!");
}

public virtual void Add(IEmployee emp)
{
    throw new Exception("这个是普通员工不能有下属!");
}

    如果要得到某个管理者的雇员列表,可以直接从Subordinates列表中获取他们,同样的,可以使用Subordinates列表返回任意一个雇员及其下属的薪水总和。

public float GetSalaries()
{
    var sum = GetSalary();    //部门领导的薪水

    var enumSub = Subordinates.GetEnumerator();
    while (enumSub.MoveNext())
    {
        var esub = (IEmployee)enumSub.Current;
        sum += esub.GetSalaries();
    }
    return sum;
}

    注意,这个方法是从当前雇员的薪水开始,每一个下属调用GetSalaries方法。当然,这是一个循环过程,任何拥有下属的雇员都会包含在内。


Boss类

    Boss类是Employee的一个子类。

public class Boss : Employee
{
    public Boss(string name, float salary) : base(name, salary) { }

    public override void Add(string name, float salary)
    {
        IEmployee emp = new Employee(name, salary);
        Subordinates.Add(emp);
    }

    public override void Add(IEmployee emp)
    {
        Subordinates.Add(emp);
    }
}

构造Employee树

    我们先创建一个CEO Employee,然后添加他的下属,再添加这些人的下属构造Employee树。

private void BuildEmployeeList()
{
    var random = new Random();

    Prez = new Boss("CEO", 200000);

    var markeVP = new Boss("市场营销副总裁", 100000);
    Prez.Add(markeVP);

    var salesMgr = new Boss("销售经理", 50000);
    var advMgr = new Boss("高级经理", 50000);

    markeVP.Add(salesMgr);
    markeVP.Add(advMgr);

    var prodVP = new Boss("产品副总裁", 10000);
    Prez.Add(prodVP);

    advMgr.Add("秘书", 20000);

    for (var i = 1; i <= 5; i++)
    {
        salesMgr.Add("销售" + i, random.Next(1000, 3000));
    }

    var prodMgr = new Boss("产品经理", 40000);
    var shipMgr = new Boss("运输经理", 35000);
    prodVP.Add(prodMgr);
    prodVP.Add(shipMgr);

    for (int i = 1; i <= 3; i++)
    {
        var shipEmp = new Employee("运输" + i, random.Next(25000));
        shipMgr.Add(shipEmp);
    }

    for (int i = 1; i <= 4; i++)
    {
        prodMgr.Add("制造" + i, random.Next(20000));
    }
}

    一旦构造好了这个组合结构,就可以创建一个可视化的TreeView列表:先创建顶端节点,然后不断的调用AddNodes()方法,直到节点中的所有叶子都加了进去。

private void BuildTree()
{
    var node = new EmpNode(Prez);
    empTree.Nodes.Add(node);
    AddNodes(node, Prez);

    empTree.ExpandAll();
}

private void AddNodes(EmpNode node, IEmployee prez)
{
    var subordinate = prez.GetSubordinate();
    while (subordinate.MoveNext())
    {
        var subordinateEmp = (IEmployee)subordinate.Current;
        var empNode = new EmpNode(subordinateEmp);
        node.Nodes.Add(empNode);
        if (!subordinateEmp.IsLeaf())
        {
            AddNodes(empNode, subordinateEmp);
        }
    }
}

    为了简化TreeNode对象的处理,我们派生一个EmpNode类,它将一个Employee实例作为参数。

    public class EmpNode : TreeNode
    {
        private IEmployee Emp { get; set; }

        public EmpNode(IEmployee emp)
            : base(emp.GetName())
        {
            Emp = emp;
        }

        public IEmployee GetEmployee()
        {
            return Emp;
        }
    }

    最终程序如下图

    在这个程序的实现里,单击一个雇员,他的成本(薪水的总和)会显示在底部。

private void empTree_AfterSelect(object sender, TreeViewEventArgs e)
{ 
    var node = (EmpNode)empTree.SelectedNode;
    GetNodeSum(node);
}
private void GetNodeSum(EmpNode node)
{
    var emp = node.GetEmployee();
    var sum = emp.GetSalaries();
    lbSalary.Text = string.Format("雇员成本:{0}", sum);
}

自我升职

    我们假设有这样一种情况,一个基层雇员还保留现有的工作,但他拥有了新的下属,例如,要求一名销售员去指导销售赏,对于这种情况,比较方便的做法是:在Boxx类中提供一个方法,它将Employee转成Boss.这里另外提供一个构造函数,它将一个雇员转换成老板。

public Boss(IEmployee emp) : base(emp.GetName(), emp.GetSalary()) { }

 项目下载地址(vs2012)

posted @ 2013-06-04 15:04  Sandglass  阅读(1004)  评论(2编辑  收藏  举报