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