• 设计模式组合模式


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

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

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

        我们考虑一个实际的需求,一个小公司,初期只有一个人,他当然就是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)

    一个完整的人生应该是宽恕、容忍、等待和爱!
  • 相关阅读:
    常用验证函数isset()/empty()/is_numeric()函数
    jquery select取option的value值发生变化事件
    (转)浅谈HTML5与css3画饼图!
    文本框输入值文字消失常用的两种方法
    简洁的滚动代码(上下滚动)
    (转)PHP的语言结构和函数的区别
    兼容ie7的导航下拉菜单
    jquery中each()函数
    tomcat源码导入eclipse
    weblogic linux环境下新建domain
  • 原文地址:https://www.cnblogs.com/homezzm/p/3117117.html
Copyright © 2020-2023  润新知