.NET中的設計模式三:組合模式

來源:互聯網
上載者:User
設計 組合模式(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是為了將畫面上的顯示程式與背景業務資料程式分離開。畫面的顯示程式只需要關心“僱員”提供哪些介面就可以工作了,而不去過問具體的細節,比如工資的計算規則。如果需要對介面類和資料類分別進行單元測試,這樣的做法也提供了可能(也就是說,這個程式是可測試的)。測試畫面的時候可以在僱員介面上實現一些虛假的僱員類,其中的方法和屬性都是為了測試而假造的,這樣就可以測試介面的顯示是否正確。一般說來程式如果要進行單元測試,應該從設計階段就考慮程式的“可測試性”,其中重要的一點是:將介面表示與商務邏輯分離開。

關於如何提高程式的可測試性,以後有時間我會整理一些心得體會。



相關文章

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。