.NET中的設(shè)計(jì)模式三:組合模式
2024-07-10 13:03:10
供稿:網(wǎng)友
組合模式(composite)是一種“結(jié)構(gòu)型”模式(structural)。結(jié)構(gòu)型模式涉及的對(duì)象為兩個(gè)或兩個(gè)以上,表示對(duì)象之間的活動(dòng),與對(duì)象的結(jié)構(gòu)有關(guān)。
先舉一個(gè)組合模式的小小例子:
如圖:系統(tǒng)中有兩種box:game box和internet box,客戶(hù)需要了解者兩個(gè)類(lèi)的接口分別進(jìn)行調(diào)用。為了簡(jiǎn)化客戶(hù)的工作,創(chuàng)建了xbox類(lèi),程序代碼如下:
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的方法,這樣,用戶(hù)面對(duì)的情況就大大的簡(jiǎn)化了,調(diào)用的代碼如下:
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();
}
}
可以看見(jiàn),用戶(hù)只需要了解xbox的接口就可以了。
組合模式的應(yīng)用例子
組合模式適用于下面這樣的情況:兩個(gè)或者多個(gè)類(lèi)有相似的形式,或者共同代表某個(gè)完整的概念,外界的用戶(hù)也希望他們合而為一,就可以把這幾個(gè)類(lèi)“組合”起來(lái),成為一個(gè)新的類(lèi),用戶(hù)只需要調(diào)用這個(gè)新的類(lèi)就可以了。
下面舉一個(gè)例子說(shuō)明composite模式的一個(gè)實(shí)際應(yīng)用。下面的class視圖:
employee類(lèi)是abstractemployee接口的一個(gè)實(shí)現(xiàn),boss類(lèi)是employee的一個(gè)子類(lèi),empnode是從樹(shù)視圖的treenode類(lèi)繼承而來(lái)的。我們先看看代碼:
abstractemployee,這是一個(gè)接口,提供下列方法:
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類(lèi)是abstractemployee接口的一個(gè)實(shí)現(xiàn)
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接口和他的一個(gè)實(shí)現(xiàn)來(lái)看,下面很可能要將這個(gè)類(lèi)型的數(shù)據(jù)組合成一個(gè)樹(shù)的結(jié)構(gòu)。
boss類(lèi)是employee類(lèi)的派生,他重載了employee類(lèi)的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方法是一個(gè)遞歸調(diào)用,如果child不是leaf,就繼續(xù)調(diào)用下去。上面幾個(gè)類(lèi)表達(dá)了一個(gè)樹(shù)的結(jié)構(gòu),表示出了公司中的領(lǐng)導(dǎo)和雇員的級(jí)別關(guān)系。
現(xiàn)在我們看一下這個(gè)程序需要達(dá)到的目標(biāo),程序運(yùn)行后顯示下面的界面:
界面上有一個(gè)樹(shù)圖,樹(shù)上顯示某公司的人員組織結(jié)構(gòu),點(diǎn)擊這些雇員,會(huì)在下面出現(xiàn)這個(gè)人的工資。現(xiàn)在程序中有兩棵樹(shù):一棵是畫(huà)面上實(shí)際的樹(shù),另一個(gè)是公司中雇員的虛擬的樹(shù)。畫(huà)面上的樹(shù)節(jié)點(diǎn)是treenode類(lèi)型,雇員的虛擬樹(shù)節(jié)點(diǎn)是abstractemployee類(lèi)型。我們可以采用組合模式,創(chuàng)造一種新的“節(jié)點(diǎn)”,組合這兩種節(jié)點(diǎn)的特性,簡(jiǎn)化窗體類(lèi)需要處理的情況,請(qǐng)看下面的代碼:
public class empnode:treenode {
private abstractemployee emp;
public empnode(abstractemployee aemp ):base(aemp.getname ()) {
emp = aemp;
}
//-----
public abstractemployee getemployee() {
return emp;
}
}
empnode類(lèi)是treenode類(lèi)的子類(lèi),他具有treenode類(lèi)的所有特性,同時(shí)他也組合了abstractemployee類(lèi)型的特點(diǎn)。這樣以來(lái)調(diào)用者的工作就簡(jiǎn)化了。下面是form類(lèi)的代碼片斷,我把自動(dòng)生成的代碼省略了一部分:
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方法是樹(shù)圖點(diǎn)擊節(jié)點(diǎn)事件的響應(yīng)方法,用戶(hù)點(diǎn)擊節(jié)點(diǎn)后在文本欄里顯示相應(yīng)的工資。組合模式已經(jīng)介紹完了,下面的東西和組合模式?jīng)]有什么關(guān)系。
為什么用interface
為什么要在程序中創(chuàng)建雇員的interface呢?我們可以創(chuàng)建一個(gè)class employee,再派生出boss,一樣可以實(shí)現(xiàn)上面的功能嘛。
使用interface是為了將畫(huà)面上的顯示程序與后臺(tái)的業(yè)務(wù)數(shù)據(jù)程序分離開(kāi)。畫(huà)面的顯示程序只需要關(guān)心“雇員”提供哪些接口就可以工作了,而不去過(guò)問(wèn)具體的細(xì)節(jié),比如工資的計(jì)算規(guī)則。如果需要對(duì)界面類(lèi)和數(shù)據(jù)類(lèi)分別進(jìn)行單元測(cè)試,這樣的做法也提供了可能(也就是說(shuō),這個(gè)程序是可測(cè)試的)。測(cè)試畫(huà)面的時(shí)候可以在雇員接口上實(shí)現(xiàn)一些虛假的雇員類(lèi),其中的方法和屬性都是為了測(cè)試而假造的,這樣就可以測(cè)試界面的顯示是否正確。一般說(shuō)來(lái)程序如果要進(jìn)行單元測(cè)試,應(yīng)該從設(shè)計(jì)階段就考慮程序的“可測(cè)試性”,其中重要的一點(diǎn)是:將界面表示與業(yè)務(wù)邏輯分離開(kāi)。
關(guān)于如何提高程序的可測(cè)試性,以后有時(shí)間我會(huì)整理一些心得體會(huì)。