首页 > 编程 > .NET > 正文

.NET中的设计模式三:组合模式

2024-07-10 13:03:10
字体:
来源:转载
供稿:网友

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

关于如何提高程序的可测试性,以后有时间我会整理一些心得体会。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表