.NET中的设计模式三:组合模式 选择自 lane_cn 的 Blog
来源:互联网 发布:杭州数游软件 编辑:程序博客网 时间:2024/05/16 19:16
组合模式(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 { 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 subordinateIEnumerator 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是为了将画面上的显示程序与后台的业务数据程序分离开。画面的显示程序只需要关心“雇员”提供哪些接口就可以工作了,而不去过问具体的细节,比如工资的计算规则。如果需要对界面类和数据类分别进行单元测试,这样的做法也提供了可能(也就是说,这个程序是可测试的)。测试画面的时候可以在雇员接口上实现一些虚假的雇员类,其中的方法和属性都是为了测试而假造的,这样就可以测试界面的显示是否正确。一般说来程序如果要进行单元测试,应该从设计阶段就考虑程序的“可测试性”,其中重要的一点是:将界面表示与业务逻辑分离开。
关于如何提高程序的可测试性,以后有时间我会整理一些心得体会。
- .NET中的设计模式三:组合模式 选择自 lane_cn 的 Blog
- .NET中的设计模式一:工厂模式 选择自 lane_cn 的 Blog
- .NET中的设计模式二:单件模式 选择自 lane_cn 的 Blog
- .NET中的设计模式五:观察者模式 选择自 lane_cn 的 Blog
- .NET中的设计模式四:命令模式 选择自 lane_cn 的 Blog
- .NET中的设计模式三:组合模式
- java多线程设计模式 选择自 zdmilan 的 Blog
- Singleton模式 选择自 xuyongshuo 的 Blog
- 设计模式-建造模式(一碗牛肉面的制造过程) 选择自 ClampHammer 的 Blog
- 设计模式-简单工厂模式(SimpleFactory-C#) 选择自 ClampHammer 的 Blog
- 设计模式笔记-抽象工厂模式 选择自 ClampHammer 的 Blog
- 设计模式之Factory深入研究 选择自 fasttalk 的 Blog
- 探索软件设计模式(一)选择自 UserQi 的 Blog
- .NET中的xpath 选择自 Uncommon 的 Blog
- ASP.Net中的MD5加密 选择自 catally 的 Blog
- asp.net 2.0中的ValidationGroup 选择自 mdot 的 Blog
- 设计模式(三):组合模式
- JDK中的设计模式之组合模式
- 思归的“动态控件的状态问题”的分析
- .NET中的设计模式四:命令模式 选择自 lane_cn 的 Blog
- 有趣的定律3. 木桶定律
- boost 最新版下载(1.32.0)(加入对象XML序列化特性)
- ASP.Net 1.x 中 __doPostBack实现的问题
- .NET中的设计模式三:组合模式 选择自 lane_cn 的 Blog
- 关于DOS(windows)下取得系统日期的方法:用于自动数据库备份等
- 有滚动条、固定Header的ASP.Net DataGrid实现
- 每个初学者都应该搞懂的问题
- ASP.NET编程习惯
- Eratosthenes筛法求素数
- JDK1.5最新消息
- COM FAQ 以后逐日累加
- 如何让你的VB6 IDE支持鼠标滚轮?