明輝手游網(wǎng)中心:是一個免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

軟體樣式(Design Pattern )之應(yīng)用

[摘要]軟體樣式(Design Pattern ) 之應(yīng)用 ※ 高煥堂 自從1991年以來﹐樣式觀念和理論逐漸成為物件導(dǎo)向(OO)領(lǐng)域中最熱門的話題之一。本文探討如何使用樣式﹐解決軟體上的常見問題。 什...
軟體樣式(Design Pattern )
之應(yīng)用

※ 高煥堂

自從1991年以來﹐樣式觀念和理論逐漸成為物件導(dǎo)向(OO)領(lǐng)域中最熱門的話題之一。本文探討如何使用樣式﹐解決軟體上的常見問題。



什么是樣式?
 顧名思意﹐樣式是人們遭遇到特定問題時﹐大家慣用的應(yīng)付方式。樣式可用來解決問題﹐而且是有效、可靠的。掌握愈多樣式﹐運(yùn)用愈成熟﹐就愈是杰出的設(shè)計專家。
依據(jù)樣式理論大師亞歷山大(Christopher Alexander) 之定義﹕

「樣式是某外在環(huán)境(Context) 下﹐對特定問題(Problem)的慣用解決之道(Solution)」

例如﹐在平坦的校園里(Context) ﹐下述兩項(xiàng)需求是互相沖突的﹕
◎?qū)W生需要交通工具代步。
◎校園空氣必須保持清潔。

就產(chǎn)生問題(Problem) 了﹐該如何化解呢﹖在一般校園里﹐慣用解決之道(Solution)是﹕規(guī)定在校園中只能騎自行車﹐不能騎有污染的機(jī)車和汽車。此解決之道是否有效﹐是取決于外在環(huán)境(Context) 。例如﹐在不平坦的清華大學(xué)校園中﹐上述解決之道就無效用了。于是另找解決方案來處理這問題了﹔例如﹐提供電動機(jī)車供學(xué)生在校園中使用。
自從1991年以來﹐亞歷山大的樣式理論逐漸應(yīng)用于OO軟體的設(shè)計上﹐可解決軟體設(shè)計上的問題。例如﹐軟體設(shè)計時﹐常見下述問題﹕

軟體模組(Module)之間的相依性太高﹐不易個別抽換模組﹐軟體的「軟」性就降低了。
可利用樣式解決這種問題﹐求增強(qiáng)模組之間的獨(dú)立性﹐提高軟體的彈性(Flexibility) ﹐降低軟體的維護(hù)費(fèi)用﹗

設(shè)計樣式(Design Pattern)

在Erich Gamma 等人合著的﹕

"Design Patterns: Elements of Reusable Object-Oriented Software"

一書列出23種軟體樣式﹐可解決軟體設(shè)計上的特定問題。本文舉例說明其中的3 種樣式﹐看看它們的應(yīng)用情形。
1. Factory Method 樣式
2. Abstract Factory 樣式
3. Builder樣式

首先拿個簡單程式來敘述「問題」之所在﹕

//// p1.cpp
class Integer {
int value;
public:
Integer() {value=100;}
void DisplayValue()
 {cout << value << endl;}
};

class Document {
 Integer *pi;
 public:
 Document() { pi = new Integer(); }
void DisplayData()
 {pi->DisplayValue();}
};

void main() {
 Document d;
 d.DisplayData();
}
//// end

兩類別的相依性很高﹐原因是﹕Document類別直接使用Integer 字眼﹐且使用兩次。于是﹐問題是﹕

「若必須將Integer 類別名稱改為Float 時﹐得更換 Document類別中的Integer 字眼」。

亦即﹐抽換Integer 類別時﹐會牽連到Document類別﹐抽換過程將不會很順暢﹗藉由抽象類別﹐可解決部分問題,如下﹕

//// p2.cpp
class Data {
 public:
 virtual void DisplayValue()=0;
};

class Integer : public Data {
int value;
public:
Integer(){value=100;}
 virtual void DisplayValue()
{ cout << value << endl; }
};
class Document {
 Data *pd;
 public:
 Document(){ pd=new Integer(); }
 void DisplayData()
{ pd->DisplayValue(); }
};

void main() {
 Document d;
 d.DisplayData();
}
//// end

 刪除掉1 個Integer 字眼﹐已長進(jìn)些了﹐不是嗎﹖但問題尚未全部解決﹗Integer 字眼仍留在Document類別內(nèi)﹐還是難分難解﹗

Abstract Factory樣式

 使用Abstract Factory樣式可解決上述問題﹗它將new Integer() 指令包裝(Encapsulate) 起來﹐Integer 字眼就不再出現(xiàn)于Document類別中。
 樣式類別將Document與Integer 兩類別隔開來﹐令其互相獨(dú)立﹐以利于個別抽換。

圖1樣式是協(xié)調(diào)者

樣式好象馬路的安全島或分道欄:


 圖2 樣式像馬路的分道欄

依照Erich Gamma 書中的Abstract Factory樣式﹐應(yīng)定義如下類別﹕

//// p3.cpp
//// Server Classes
class Data {
 public:
 virtual void DisplayValue()=0;
 };
class Integer : public Data {
int value;
public:
Integer(){ value=100; }
void DisplayValue()
 { cout << value << endl; }
};

//// Pattern Classes
class Factory {
 public:
 virtual Data *CreateDataObject()=0;
};
class IntFactory : public Factory {
 public:
 virtual Data *CreateDataObject()
 { return new Integer(); }
};
////Client classes
class Document {
 Data *pd;
public:
 Document(Factory *pf)
{pd = pf->CreateDataObject();}
 void DisplayData()
{pd->DisplayValue();}
};
void main() {
 Document d(new IntFactory);
 d.DisplayData();
}
//// end

這Abstract Factory樣式內(nèi)含F(xiàn)actory Method樣式。上述Document類別不再使用Integer 字眼了﹐這個好現(xiàn)象﹐就是樣式創(chuàng)造出來的效果,如圖3 所示。 
因?yàn)镃lient部分僅使用1 次IntFactory字眼﹐且不會用到Integer 字眼﹔于是﹐就容易抽換Integer 類別了。例如﹐也定義Float 類別﹐以及定義FloatFactory類別如下圖4 所示。
 假設(shè)有一天﹐必須將Integer 改為Float 時﹐只需更動main()函數(shù)如下﹕

 ....
 void main()
 {
 Document d( new FloatFactory );
 d.DisplayData();
 }
 ....

只更動一個指令而已﹐其它都保持原狀﹐很棒吧﹗
 上述的Factory 類別及其子類別﹐合起來稱為Abstract Factory樣式。其中的CreateDataObject()虛擬函數(shù)之用法則稱為Factory Method樣式。兩者共同創(chuàng)造出Client部分與Server部分之獨(dú)立性。




圖3Abstract Factory樣式之角色





圖4Client 類別與Server之介面


Abstract Factory樣式之原理

 圖4 表達(dá)了﹕Document類別只使用抽象類別Factory 和Data之名稱﹐并使用這些抽象類別之函數(shù)。抽象類別定義了其子類別之共同介面(Interface),而子類別定義其內(nèi)涵(Implementation)。于是﹐Document類別之設(shè)計者只需知道Server之介面﹐不需了解其內(nèi)涵。只要介面保持穩(wěn)定﹐Server之內(nèi)部更動時﹐并不影響Client類別之內(nèi)容﹐就創(chuàng)造出彈性和獨(dú)
立性。這充分表現(xiàn)OO設(shè)計的精神﹕
"Program To Interface"
(針對介面﹐而非針對內(nèi)涵)

使用Builder 樣式
從上述Abstract Factory樣式之應(yīng)用中﹐可歸納出兩個基本動作﹕

 


圖5 Client與Server類別相依性高


1.把Server類別之名稱(例如﹐new
Integer )包裝于具體類別(Concrete
Class) 之中。
2.利用Factory Method樣式﹐將虛擬函
數(shù)定義于抽象類別內(nèi)﹐供Client 類別
使用之。

這方法也可應(yīng)用于較復(fù)雜的組合類別(Composite Class) 上﹐如上圖5 所示。
Document類別負(fù)擔(dān)組合(Composition) 之工作﹐亦即誕生Age 及Score 物件﹐并將它們組合成StudentRecord 物件。所以Document類別直接使用StudentRecord 、Age 及Score 名稱﹐且負(fù)責(zé)誕生物件﹐如下﹕

//// p4.h
class Age {
int value;
public:
Age( int v ) : value(v){}
void DisplayValue()
 { cout << value << endl; }
};
class Score {
 float value;
 public:
 Score(float v)=value(v){}
 void DisplayValue()
{cout << value << endl;}
};
class StudentRecord {
 char name[10];
 Age *pa;
 Score *ps[2];
 int index;
 public:
 StudentRecord(char *na)
{strcpy(name, na);
 index=0;
}
 void SetAge(Age *pAge)
 {pa=pAge;}
 void AddScore(Score *pscore)
{ps[index++]=pscore;}
 void Display()
{cout << name << endl;
 pa->DisplayValue();
 for( int i=0; i<index; i++ )
ps->DisplayValue();
}
 ~Student()
{delete pa; delete ps[]; }
};

//// p5.cpp
#include "p4.h"
class Document
{
//....
StudentRecord *pst;
public:
Document(char *na)
{pst=new StudentRecord(na);
 pst->SetAge(new Age(20));
 pst->AddScore(new Score(90.5));
 pst->AddScore(new Score(88.8));
 }
//....
};

void main() {
 Document d("John");
 d.Display();
}
//// end

Document類別含有new Age 等指令﹐應(yīng)將之包裝起來﹐并使用Factory Method樣式﹐將虛擬函數(shù)擺入抽象類別中﹐如下圖6。

 


 圖6Builder樣式之角色


 上述圖5 與圖6 的分別是﹕圖6 使用Builder 樣式時﹐Document只使用Builder 類別定義之介面﹐未用到Age 和Score 類別。所以只要Builder 保持不變﹐就可抽換StudentRecord 、Age 等具體類別之內(nèi)函﹐而不會牽連到Document之內(nèi)容。此外﹐SRBuilder 具體類別包裝了StudentRecord 之組合「過程」﹐Document之設(shè)計者不必考慮到如何將Age 、Score 等物件組合成為StudentRecord 大物件﹐就創(chuàng)造了Document與StudentRecord 之間的獨(dú)立性。假若有一天﹐必須改變其組合之「過程」﹐并不必更動Document及其它Client類別之內(nèi)容﹐彈性由然而生了。以C++ 表達(dá)Builder 樣式如下﹕

////p6.h
class Builder {
public:
virtual void BuildSR()=0;
virtual void BuildAge(int v)=0;
virtual void BuildScore(float s1)=0;
virtual void BuildScore(float s1, float s2)=0;
virtual StudentRecord *GetResult()=0;
};

class SRBuilder {
 StudentRecord *curr;
public:
 void BuilderSR(name)
 {curr=new StudentRecord(name);}
 void BuildAge(int v)
 {curr->SetAge(v);}
 void BuildScore(float s)
 {curr->AddScore(s);}
 void BuildScore(float s1, float s2)
 {curr->AddScore(s1);
curr->AddScore(s2);
}
 StudentRecord *GetResult()
 {return curr;}
};

////p7.cpp
#include "p4.h"
#include "p6.h"
class Document {
Builder *pb;
public:
Document(Builder *builder)
{pb=builder;}
void BuildStudentRecord(char *na)
{pb->BuildSR(na);
pb->BuildAge(20);
pb->BuildScore(98.5, 80.25);
}
void Display()
 { pb->GetResult()->Display(); }
};

void main() {
 Document d( new SRBuilder );
 d.Display();
}
//// end

從上可知﹐理想的軟體師應(yīng)不斷追求軟體的彈性﹐軟體才能生生不息。軟體欠缺「彈性」﹐是傳統(tǒng)軟體危機(jī)的主因﹐只要是在某前提下(Context) ﹐樣式就能提供有效的解決之道。