(1)設(shè)計(jì)模式簡(jiǎn)介
以下代碼能夠?qū)崿F(xiàn)對(duì)圖形的繪制:
class Point{
public:
int x;
int y;
};
class Line{
public:
Point start;
Point end;
Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
};
class Rect{
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
vector<Line> lineVector;
vector<Rect> rectVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
Line line(p1, p2);
lineVector.push_back(line);
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
Rect rect(p1, width, height);
rectVector.push_back(rect);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//針對(duì)直線
for (int i = 0; i < lineVector.size(); i++){
e.Graphics.DrawLine(Pens.Red,
lineVector[i].start.x,
lineVector[i].start.y,
lineVector[i].end.x,
lineVector[i].end.y);
}
//針對(duì)矩形
for (int i = 0; i < rectVector.size(); i++){
e.Graphics.DrawRectangle(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
//...
Form::OnPaint(e);
}
上述代碼體現(xiàn)了一種分而治之的思想,在MainForm的類里面對(duì)顯示界面的每個(gè)形狀都進(jìn)行了維護(hù)和處理。
在上述代碼中只是對(duì)于矩形、直線和點(diǎn)三種形式的圖形進(jìn)行了繪制方式的設(shè)定,若此時(shí)想繪制一個(gè)圓,此時(shí)代碼不能提供相對(duì)應(yīng)的方法,這也就意味著代碼將會(huì)發(fā)生變化。
需增加一個(gè)圓的類class Circle,同時(shí)還需要進(jìn)行如下更改:
class MainForm : public Form {
private:
Point p1;
Point p2;
vector<Line> lineVector;
vector<Rect> rectVector;
//改變
vector<Circle> circleVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
Line line(p1, p2);
lineVector.push_back(line);
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
Rect rect(p1, width, height);
rectVector.push_back(rect);
}
//改變
else if (...){
//...
circleVector.push_back(circle);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//針對(duì)直線
for (int i = 0; i < lineVector.size(); i++){
e.Graphics.DrawLine(Pens.Red,
lineVector[i].start.x,
lineVector[i].start.y,
lineVector[i].end.x,
lineVector[i].end.y);
}
//針對(duì)矩形
for (int i = 0; i < rectVector.size(); i++){
e.Graphics.DrawRectangle(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
//改變
//針對(duì)圓形
for (int i = 0; i < circleVector.size(); i++){
e.Graphics.DrawCircle(Pens.Red,
circleVector[i]);
}
//...
Form::OnPaint(e);
}
上述修改的代碼段比較分散,如果站在軟件工程的角度上來看,對(duì)于修改過的部分都需要在重新測(cè)試等一些列的操作,其實(shí)對(duì)于功能擴(kuò)充來說其實(shí)面對(duì)著后期大量的工作。
關(guān)于抽象的設(shè)計(jì)思路:
class Shape{
public:
virtual void Draw(const Graphics& g)=0;
virtual ~Shape() { }
};
class Point{
public:
int x;
int y;
};
class Line: public Shape{
public:
Point start;
Point end;
Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
//實(shí)現(xiàn)自己的Draw,負(fù)責(zé)畫自己
virtual void Draw(const Graphics& g){
g.DrawLine(Pens.Red,
start.x, start.y,end.x, end.y);
}
};
class Rect: public Shape{
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}
//實(shí)現(xiàn)自己的Draw,負(fù)責(zé)畫自己
virtual void Draw(const Graphics& g){
g.DrawRectangle(Pens.Red,
leftUp,width,height);
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
//針對(duì)所有形狀
vector<Shape*> shapeVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
shapeVector.push_back(new Line(p1,p2));
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
shapeVector.push_back(new Rect(p1, width, height));
}
else if (...){
//...
shapeVector.push_back(circle);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//針對(duì)所有形狀
for (int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); //多態(tài)調(diào)用,各負(fù)其責(zé)
}
//...
Form::OnPaint(e);
}
對(duì)于每個(gè)形狀,都增加了一個(gè)共同的父類Shape,同時(shí)對(duì)于圖形來說應(yīng)該如何繪制自己的這樣的形狀的函數(shù)都是由自己來實(shí)現(xiàn)的(Draw()函數(shù))。MainForm只需要管理Shape的指針,而不再具體管理某一個(gè)具體的形狀,實(shí)現(xiàn)了一次向上抽象的管理。同時(shí)onPaint的方法來說,也不在直接管理繪制的問題,而是調(diào)用了Shape中的虛函數(shù)Draw,通過多態(tài)調(diào)用來實(shí)現(xiàn)了MainForm來調(diào)用了自己繪制自己的方法。
此時(shí)若再次需要添加一個(gè)對(duì)圓形的處理,則其偽碼如下:
class Shape{
public:
virtual void Draw(const Graphics& g)=0;
virtual ~Shape() { }
};
class Point{
public:
int x;
int y;
};
class Line: public Shape{
public:
Point start;
Point end;
Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
//實(shí)現(xiàn)自己的Draw,負(fù)責(zé)畫自己
virtual void Draw(const Graphics& g){
g.DrawLine(Pens.Red,
start.x, start.y,end.x, end.y);
}
};
class Rect: public Shape{
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}
//實(shí)現(xiàn)自己的Draw,負(fù)責(zé)畫自己
virtual void Draw(const Graphics& g){
g.DrawRectangle(Pens.Red,
leftUp,width,height);
}
};
//增加
class Circle : public Shape{
public:
//實(shí)現(xiàn)自己的Draw,負(fù)責(zé)畫自己
virtual void Draw(const Graphics& g){
g.DrawCircle(Pens.Red,
...);
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
//針對(duì)所有形狀
vector<Shape*> shapeVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
shapeVector.push_back(new Line(p1,p2));
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
shapeVector.push_back(new Rect(p1, width, height));
}
//改變
else if (...){
//...
shapeVector.push_back(circle);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//針對(duì)所有形狀
for (int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); //多態(tài)調(diào)用,各負(fù)其責(zé)
}
//...
Form::OnPaint(e);
}
以上可以看出解決了之前代碼所存在問題,由于這樣的抽象的思想,使得代碼的變更更加容易,重用性得到了提升。
什么是好的軟件設(shè)計(jì)呢?軟件設(shè)計(jì)的金科玉律:復(fù)用。
變化是復(fù)用最大的天敵!面向?qū)ο蟮淖畲髢?yōu)勢(shì)就在于:抵御變化。
(2)面向?qū)ο笤O(shè)計(jì)原則
<1>依賴倒置原則(DIP)
1)高層模塊(穩(wěn)定的)不應(yīng)該依賴于低層模塊(容易變化的),二者都應(yīng)該依賴于抽象(穩(wěn)定的);
2)抽象(穩(wěn)定的)不應(yīng)該依賴于實(shí)現(xiàn)細(xì)節(jié)(容易變化的),實(shí)現(xiàn)細(xì)節(jié)應(yīng)該依賴于抽象(穩(wěn)定的)。
<2>開放封閉原則(OCP)
1)對(duì)擴(kuò)展開放,對(duì)更改封閉;
2)模塊應(yīng)該是可擴(kuò)展的,但是不可修改。
<3>單一指責(zé)原則(SRP)
1)一個(gè)類應(yīng)該僅有一個(gè)引起它變化的原因;
2)變化的方向隱含著類的責(zé)任。
<4>Liskov替換原則(LSP)
1)子類必須能夠替換他們的基類(is-a);
2)集成表達(dá)抽象類型。
<5>接口隔離原則(ISP)
1)不應(yīng)該強(qiáng)迫客戶程序依賴他們不用的方法;
2)接口應(yīng)該小而完備。
<6>優(yōu)先使用對(duì)象組合,而不是類繼承
1)類繼承通常為“白盒復(fù)用”,對(duì)象組合通常為“黑箱復(fù)用”;
2)繼承在某種程度上破壞了封裝性,子類父類耦合度高;
3)而對(duì)象組合則只要求被組合的對(duì)象具有良好定義的接口,耦合度低。
<7>封裝變化點(diǎn)
使用封裝來創(chuàng)建對(duì)象之間的分界層,讓設(shè)計(jì)者可以在分界層的一側(cè)進(jìn)行修改,而不會(huì)對(duì)另一側(cè)產(chǎn)生不良的影響,從而實(shí)現(xiàn)層次間的松耦合。
<8>針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程
1)不講變量類型聲明為某個(gè)特定的具體類,而是聲明為某個(gè)接口;
2)客戶程序無需獲知對(duì)象的具體類型,只需要知道對(duì)象所具有的接口;
3)減少系統(tǒng)中個(gè)部分的依賴關(guān)系,從而實(shí)現(xiàn)“高內(nèi)聚、松耦合”的類型設(shè)計(jì)方案。
產(chǎn)業(yè)強(qiáng)盛的標(biāo)志:接口的標(biāo)準(zhǔn)化
GOF-23模式分類方式:
<1>依據(jù)目的分類
1)創(chuàng)建型(Creational)模式
2)結(jié)構(gòu)型(Structural)模式
3)行為型(Behavioral)模式
<2>依據(jù)范圍分類
1)類模式處理與子類的靜態(tài)關(guān)系
2)對(duì)象模式處理間的動(dòng)態(tài)關(guān)系
(3)模板方法
定一個(gè)操作中的算法的骨架(穩(wěn)定的),而將一些步驟延遲(容易變化的)到子類中。Template Method使得子類可以不改變(復(fù)用)一個(gè)算法的結(jié)構(gòu)即可重新定義(Override覆寫)該算法的某寫特定步驟
——《設(shè)計(jì)模式(GoF)》
對(duì)于庫(kù)的開發(fā)人員來說,已經(jīng)開發(fā)完成了所需要功能的1、3、5三個(gè)步驟;而如果要實(shí)現(xiàn)這個(gè)完整功能,需要應(yīng)用程序的開發(fā)人員實(shí)現(xiàn)其中的2和4這兩步。同時(shí)還需要應(yīng)用程序的開發(fā)者在使用的時(shí)候依次來調(diào)用庫(kù)和自己開發(fā)的這幾個(gè)函數(shù)。
//程序庫(kù)開發(fā)人員
class Library{
public:
void Step1(){
//...
}
void Step3(){
//...
}
void Step5(){
//...
}
};
//應(yīng)用程序開發(fā)人員
class Application{
public:
bool Step2(){
//...
}
void Step4(){
//...
}
};
int main()
{
Library lib();
Application app();
lib.Step1();
if (app.Step2()){
lib.Step3();
}
for (int i = 0; i < 4; i++){
app.Step4();
}
lib.Step5();
}
上述開發(fā)方式存在著以下問題:
<1>對(duì)應(yīng)用程序的開發(fā)人員來說,他需要自己開發(fā)其中的第二和第五步的函數(shù)的開發(fā)。對(duì)于應(yīng)用程序的開發(fā)者來說,要求是比較高的,必須對(duì)庫(kù)中的函數(shù)情況比較了解,重寫的兩個(gè)函數(shù)的難度也相對(duì)較大,對(duì)于函數(shù)整體執(zhí)行流程也不被庫(kù)函數(shù)的開發(fā)人員所控制。
<2>庫(kù)開發(fā)人員和應(yīng)用程序的開發(fā)人員所開發(fā)的內(nèi)容的耦合度很高,彼此相互交織在一起,還需要由用開發(fā)人員來組織整體調(diào)用流程。未來程序的擴(kuò)展性和可維護(hù)性的難度都比較大。
通過模板方法可以解決以上問題:
//程序庫(kù)開發(fā)人員
class Library{
public:
//穩(wěn)定 template method
void Run(){
Step1();
if (Step2()) { //支持變化 ==> 虛函數(shù)的多態(tài)調(diào)用
Step3();
}
for (int i = 0; i < 4; i++){
Step4(); //支持變化 ==> 虛函數(shù)的多態(tài)調(diào)用
}
Step5();
}
virtual ~Library(){ }
protected:
void Step1() { //穩(wěn)定
//.....
}
void Step3() {//穩(wěn)定
//.....
}
void Step5() { //穩(wěn)定
//.....
}
virtual bool Step2() = 0;//變化
virtual void Step4() =0; //變化
};
//應(yīng)用程序開發(fā)人員
class Application : public Library {
protected:
virtual bool Step2(){
//... 子類重寫實(shí)現(xiàn)
}
virtual void Step4() {
//... 子類重寫實(shí)現(xiàn)
}
};
int main()
{
Library* pLib=new Application();
lib->Run();
delete pLib;
}
}
能夠開發(fā)庫(kù)的開發(fā)人員能增加了兩個(gè)虛函數(shù),同時(shí)庫(kù)的開發(fā)人員定義了一個(gè)run方法,在run方法中,按照步驟調(diào)用了按照規(guī)則來調(diào)用的幾個(gè)方法。
此時(shí)應(yīng)用程序的開發(fā)人員只需要重寫庫(kù)函數(shù)中定義的函數(shù)。這樣不僅避免了可能出現(xiàn)的缺陷,同時(shí)對(duì)于應(yīng)用程序來說使用庫(kù)的流程變得極其簡(jiǎn)單,只需要調(diào)用run方法即可,而不需要考慮具體的調(diào)用流程和規(guī)則。
對(duì)于Template Method來說,有一個(gè)前提,就是Run()方法必須要是穩(wěn)定的。如果Run()不穩(wěn)定,那么沒有一個(gè)穩(wěn)定的軟件的骨架,就不存在這樣一種設(shè)計(jì)模式。假定,所有方式都是穩(wěn)定,那么其實(shí)也沒有必要使用設(shè)計(jì)模式。
Template Method是一種非?;A(chǔ)性的設(shè)計(jì)模式,在面向?qū)ο蟮南到y(tǒng)中有著大量的應(yīng)用。他用最簡(jiǎn)潔的機(jī)制(虛函數(shù)的多態(tài)性)為很多應(yīng)用程序的框架提供了靈活的擴(kuò)展點(diǎn),是代碼復(fù)用方面的基本實(shí)現(xiàn)結(jié)構(gòu)。
除了可以靈活對(duì)應(yīng)子步驟的變化外,“不要調(diào)用我,讓我來調(diào)用你”的反向控制結(jié)構(gòu)是Template Method的典型應(yīng)用。
在具體實(shí)現(xiàn)方面,被Template Method調(diào)用的虛方法可以具有實(shí)現(xiàn),也可以沒有任何實(shí)現(xiàn)(抽象方法、純虛方法),一般推薦將他們?cè)O(shè)置為protected方法。
(4)策略模式
定義一系列算法,把他們一個(gè)個(gè)封裝起來,并且是他們可以互相替換(變化)。該模式似的算法可以獨(dú)立于使用它的客戶程序(穩(wěn)定的)而變化(擴(kuò)展,子類化)。
——《設(shè)計(jì)模式》GoF
策略模式的動(dòng)機(jī)為:在軟件構(gòu)建的過程中,某些對(duì)象使用的算法可能是多種多樣的,經(jīng)常改變,如果將這些算法都編碼到對(duì)象中,將會(huì)使對(duì)象變得異常復(fù)雜;而且有時(shí)候支持不使用的算法也是一種性能負(fù)擔(dān)。
比如當(dāng)前有一種稅種的邏輯處理機(jī)制:
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax,
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
//....
}
};
若此時(shí)需要增加一種稅種:
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax,
FR_Tax //更改
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
else if (tax == FR_Tax){ //更改
//...
}
//....
}
};
不但需要修改TaxBase中的參數(shù),還需要在SalseOrder中添加關(guān)于新增加稅種的一個(gè)判斷,然后才能進(jìn)行相應(yīng)的計(jì)算。此時(shí)違反了對(duì)開閉原則,同時(shí)也違反了反向依賴的原則。
使用策略模式:
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class SalesOrder{
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val =
strategy->Calculate(context); //多態(tài)調(diào)用
//...
}
};
此時(shí)再增加一種稅種:
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
//擴(kuò)展
//*********************************
class FRTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//.........
}
};
class SalesOrder{
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val =
strategy->Calculate(context); //多態(tài)調(diào)用
//...
}
};
SalesOrder的CalculateTax函數(shù)多態(tài)調(diào)用了TaxStrategy類中的Calucate方法。而TaxStrategy的每個(gè)子類都是一種新的稅種計(jì)算方式,每個(gè)稅種計(jì)算中還實(shí)現(xiàn)了自己對(duì)應(yīng)的稅種計(jì)算的方法。
在擴(kuò)展的時(shí)候,需要增加TaxStrategy的子類就行了,不需要在改變SalesOrder的中的代碼了。
總結(jié):
<1>Strategy及其子類為組件提供了一系列可重用的算法,從而可以使得類型在運(yùn)行時(shí)方便的根據(jù)需要在各個(gè)算法之間進(jìn)行切換。
<2>Strategy模式提供了用條件判斷語(yǔ)句的另一種選擇,消除條件判斷語(yǔ)句,就是在解耦合。含有許多條件判斷語(yǔ)句的代碼通常都需要Strategy模式。尤其是條件判斷語(yǔ)句在未來會(huì)有增加可能性的時(shí)候,應(yīng)該優(yōu)先考慮Strategy模式。
<3>如果Strategy對(duì)象沒有實(shí)例變量,那么各個(gè)上下文可以共享同一個(gè)Strategy對(duì)象,從而節(jié)省對(duì)象的開銷。
(5)觀察者模式
觀察者模式的動(dòng)機(jī):
在軟件構(gòu)建的過程中,我們需要為某些對(duì)象建立一種“通知依賴關(guān)系”——一個(gè)對(duì)象(目標(biāo)對(duì)象)的狀態(tài)發(fā)生改變,所有的以來對(duì)象(觀察者對(duì)象)都將得到通知。如果這樣的依賴關(guān)系過于緊密,將使得軟件不能很好的抵御變化。
使用面向?qū)ο蠹夹g(shù),可以將這種依賴關(guān)系弱化,并形成一種穩(wěn)定的依賴關(guān)系。從而實(shí)現(xiàn)軟件體系結(jié)構(gòu)的松耦合。
以下是一個(gè)文件分割器的偽碼,其中存在一個(gè)進(jìn)度條:
class FileSplitter
{
string m_filePath;
int m_fileNumber;
ProgressBar* m_progressBar; // 產(chǎn)生編譯式依賴,實(shí)際是一個(gè)通知
public:
FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
m_filePath(filePath),
m_fileNumber(fileNumber),
m_progressBar(progressBar){
}
void split(){
//1.讀取大文件
//2.分批次向小文件中寫入
for (int i = 0; i < m_fileNumber; i++){
//...
// 更新進(jìn)度條數(shù)據(jù)
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue);
}
}
};
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, progressBar);
splitter.split();
}
};
在以上的設(shè)計(jì)方法上,前端界面依賴了一個(gè)ProgressBar,是一種實(shí)現(xiàn)細(xì)節(jié),而實(shí)現(xiàn)細(xì)節(jié)是極易變化的。同時(shí)其中的耦合性也很高,只要需要更改顯示的進(jìn)度的情況,就需要更改前端的界面。
由以上的依賴關(guān)系不難看出,對(duì)于分隔文件的算法相對(duì)來說穩(wěn)定,但是卻依賴了一個(gè)不穩(wěn)定的ProgressBar。
那么如果對(duì)于界面修改的部分來說,他面對(duì)的是一個(gè)通知的數(shù)組,每個(gè)數(shù)組里面都是一個(gè)抽象的通知的接口,就可以使用多態(tài)性來解決解耦的問題。
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};
class FileSplitter
{
string m_filePath;
int m_fileNumber;
List<IProgress*> m_iprogressList; // 抽象通知機(jī)制,支持多個(gè)觀察者
public:
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber){
}
void split(){
//1.讀取大文件
//2.分批次向小文件中寫入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);//發(fā)送通知
}
}
void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
virtual void onProgress(float value){
List<IProgress*>::iterator itor=m_iprogressList.begin();
while (itor != m_iprogressList.end() )
(*itor)->DoProgress(value); //更新進(jìn)度條
itor++;
}
}
};
class MainForm : public Form, public IProgress(抽象基類)
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //訂閱通知
splitter.addIProgress(&cn); //訂閱通知
splitter.split();
splitter.removeIProgress(this);
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
};
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};
對(duì)于這個(gè)解決方案來說,將ProgressBar進(jìn)行了抽象,對(duì)于FileSplitter只需要依賴一個(gè)list,由一個(gè)list來維護(hù)IProgressBar* 的列表,對(duì)于每個(gè)ProgressBar控件來說,只需要繼承IProgressBar即可。
對(duì)于MainForm來說,只需要調(diào)用比較穩(wěn)定的FileSplitter既可以來調(diào)用其中的分隔文件的方法,而在FileSplitter中已經(jīng)實(shí)現(xiàn)了遍歷list中所有IProgressBar并通知的功能,因此實(shí)現(xiàn)了觀察者模式的要求。
要點(diǎn)總結(jié):
使用面向?qū)ο蟮某橄?,Observer模式使得我們可以獨(dú)立地改變目標(biāo)與觀察者,從而使二者之間的依賴關(guān)系達(dá)到松耦合。目標(biāo)發(fā)送通知時(shí),無需制定觀察者,通知(可以攜帶通知信息作為參數(shù))會(huì)自動(dòng)傳播。觀察者自己決定是否需要訂閱通知,目標(biāo)對(duì)象對(duì)此一無所知。Observer模式是基于事件的UI框架中非常常用的設(shè)計(jì)模式,也是MVC模式的一個(gè)重要組成部分。
(6)裝飾模式
動(dòng)態(tài)(組合)地給一個(gè)對(duì)象增加一些額外的指責(zé)。就增加功能而言,Decorator模式比生成子類(繼承)更為靈活(消除重復(fù)代碼、減少子類個(gè)數(shù))。
——《設(shè)計(jì)模式》GoF
裝飾模式動(dòng)機(jī):
在某些情況下,我們可能會(huì)“過度的使用繼承來擴(kuò)展對(duì)象的功能”,由于繼承為類型引入靜態(tài)特質(zhì),使得這種擴(kuò)展方式缺乏靈活性;并且隨著子類的增多(擴(kuò)展功能的在增多),各種子類的組合(擴(kuò)展功能的組合)會(huì)導(dǎo)致子類的膨脹。
在下面有一組偽代碼,這組偽代碼主要描述一些流的操作,比如文件流、網(wǎng)絡(luò)流、內(nèi)存流等,如果我們對(duì)于流提出更多的要求,比如需要對(duì)文件流加密、對(duì)網(wǎng)絡(luò)流加密以及內(nèi)存流的加密,同時(shí)如果考慮緩沖的情況下,那么這些流都可能會(huì)要緩沖,也可能繼續(xù)的組合,產(chǎn)生加密緩沖網(wǎng)絡(luò)流等等的操作。
//業(yè)務(wù)操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主體類
class FileStream: public Stream{
public:
virtual char Read(int number){
//讀文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//寫文件流
}
};
class NetworkStream :public Stream{
public:
virtual char Read(int number){
//讀網(wǎng)絡(luò)流
}
virtual void Seek(int position){
//定位網(wǎng)絡(luò)流
}
virtual void Write(char data){
//寫網(wǎng)絡(luò)流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//讀內(nèi)存流
}
virtual void Seek(int position){
//定位內(nèi)存流
}
virtual void Write(char data){
//寫內(nèi)存流
}
};
//擴(kuò)展操作
class CryptoFileStream :public FileStream{
public:
virtual char Read(int number){
//額外的加密操作...
FileStream::Read(number);//讀文件流
}
virtual void Seek(int position){
//額外的加密操作...
FileStream::Seek(position);//定位文件流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
FileStream::Write(data);//寫文件流
//額外的加密操作...
}
};
class CryptoNetworkStream : :public NetworkStream{
public:
virtual char Read(int number){
//額外的加密操作...
NetworkStream::Read(number);//讀網(wǎng)絡(luò)流
}
virtual void Seek(int position){
//額外的加密操作...
NetworkStream::Seek(position);//定位網(wǎng)絡(luò)流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
NetworkStream::Write(data);//寫網(wǎng)絡(luò)流
//額外的加密操作...
}
};
class CryptoMemoryStream : public MemoryStream{
public:
virtual char Read(int number){
//額外的加密操作...
MemoryStream::Read(number);//讀內(nèi)存流
}
virtual void Seek(int position){
//額外的加密操作...
MemoryStream::Seek(position);//定位內(nèi)存流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
MemoryStream::Write(data);//寫內(nèi)存流
//額外的加密操作...
}
};
class BufferedFileStream : public FileStream{
//...
};
class BufferedNetworkStream : public NetworkStream{
//...
};
class BufferedMemoryStream : public MemoryStream{
//...
}
class CryptoBufferedFileStream :public FileStream{
public:
virtual char Read(int number){
//額外的加密操作...
//額外的緩沖操作...
FileStream::Read(number);//讀文件流
}
virtual void Seek(int position){
//額外的加密操作...
//額外的緩沖操作...
FileStream::Seek(position);//定位文件流
//額外的加密操作...
//額外的緩沖操作...
}
virtual void Write(byte data){
//額外的加密操作...
//額外的緩沖操作...
FileStream::Write(data);//寫文件流
//額外的加密操作...
//額外的緩沖操作...
}
};
void Process(){
//編譯時(shí)裝配
CryptoFileStream *fs1 = new CryptoFileStream();
BufferedFileStream *fs2 = new BufferedFileStream();
CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}
上述代碼的復(fù)用性不高,對(duì)于每種方式都有大量的重復(fù)代碼。這樣的設(shè)計(jì)會(huì)導(dǎo)致class的數(shù)量激增。

如果把那些因?yàn)榻M合而產(chǎn)生的class(比如CryptoFileStream)的繼承關(guān)系先打斷,而是改為使用內(nèi)部持有原先父類的指針,比如CryptoFileStream原先繼承了FileStream,改為持有FileStream。這時(shí)候原class中的調(diào)用父類的函數(shù)全都可以改為使用自己持有的指針對(duì)象來調(diào)用。此時(shí)代碼的重復(fù)性大幅度提高,只是每一個(gè)組合型的class中持有的那個(gè)指針不屬于同一個(gè)(比如,持有的為FileStream,NetworkStream以及BufferStream的指針),如果觀察一下,也不難發(fā)現(xiàn),其實(shí)FileStream,NetworkStream以及BufferStream又都有同一個(gè)父類Stream,所以利用多態(tài)性,就可以將每個(gè)組合類型中的持有的指針變量,聲明為他們共同的父類,利用多態(tài)性只需要讓父類的指向子類即可(
Stream stream = new FileStream();),這就實(shí)現(xiàn)了,編譯時(shí)都為一樣的,在運(yùn)行時(shí)提供不同的對(duì)象。此時(shí)會(huì)發(fā)現(xiàn)具有大量重復(fù)的類,合并了同類項(xiàng),減少很多不必要的組合類。
//業(yè)務(wù)操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主體類
class FileStream: public Stream{
public:
virtual char Read(int number){
//讀文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//寫文件流
}
};
class NetworkStream :public Stream{
public:
virtual char Read(int number){
//讀網(wǎng)絡(luò)流
}
virtual void Seek(int position){
//定位網(wǎng)絡(luò)流
}
virtual void Write(char data){
//寫網(wǎng)絡(luò)流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//讀內(nèi)存流
}
virtual void Seek(int position){
//定位內(nèi)存流
}
virtual void Write(char data){
//寫內(nèi)存流
}
};
//擴(kuò)展操作
class CryptoStream: public Stream {
Stream* stream;//...
public:
CryptoStream(Stream* stm):stream(stm){
}
virtual char Read(int number){
//額外的加密操作...
stream->Read(number);//讀文件流
}
virtual void Seek(int position){
//額外的加密操作...
stream::Seek(position);//定位文件流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
stream::Write(data);//寫文件流
//額外的加密操作...
}
};
class BufferedStream : public Stream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):stream(stm){
}
//...
};
void Process(){
//運(yùn)行時(shí)裝配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}
觀察上面的代碼來說,其實(shí)有很多class里面都持有同樣的變量stream,那么這些類可以向上再抽象一個(gè)父類,將strem 在父類中聲明。
//業(yè)務(wù)操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主體類
class FileStream: public Stream{
public:
virtual char Read(int number){
//讀文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//寫文件流
}
};
class NetworkStream :public Stream{
public:
virtual char Read(int number){
//讀網(wǎng)絡(luò)流
}
virtual void Seek(int position){
//定位網(wǎng)絡(luò)流
}
virtual void Write(char data){
//寫網(wǎng)絡(luò)流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//讀內(nèi)存流
}
virtual void Seek(int position){
//定位內(nèi)存流
}
virtual void Write(char data){
//寫內(nèi)存流
}
};
//擴(kuò)展操作
DecoratorStream: public Stream{
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm){
}
};
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
virtual char Read(int number){
//額外的加密操作...
stream->Read(number);//讀文件流
}
virtual void Seek(int position){
//額外的加密操作...
stream::Seek(position);//定位文件流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
stream::Write(data);//寫文件流
//額外的加密操作...
}
};
class BufferedStream : public DecoratorStream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):DecoratorStream(stm){
}
//...
};
void Process(){
//運(yùn)行時(shí)裝配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}

FileStream、NetworkStream還有BufferStream可以獨(dú)立執(zhí)行任務(wù),而加密和緩存其實(shí)就是對(duì)他們的增強(qiáng)。只需要對(duì)Stream(這三個(gè)類的共同父類)增加加密功能、增加緩沖的功能,而在使用的時(shí)候自由組合即可,而不需要把代碼全都寫出來。
總結(jié):
<1>通過采用組合而非繼承的手法,Decorator模式實(shí)現(xiàn)了在運(yùn)動(dòng)時(shí)動(dòng)態(tài)擴(kuò)展對(duì)象功能的能力,而且可以根據(jù)需要擴(kuò)展多個(gè)功能。避免了使用集成帶來的“靈活性差”和“多子類衍生的問題”。
<2>Decorator類在接口上變現(xiàn)為is-a Component的繼承關(guān)系,即Decorator類集成了Component類所具有的接口。但在實(shí)現(xiàn)上又表現(xiàn)為has-a Component的組合關(guān)系,即Decorator類又使用了另外一個(gè)Component類。
<3>Decorator模式的目的并非解決“多子類衍生的多繼承”問題,Decorator模式應(yīng)用的重要點(diǎn)在于解決“主體類在多個(gè)方向上的擴(kuò)展功能”——是為“裝飾”的含義。
(7)橋模式
將抽象部分(業(yè)務(wù)功能)與實(shí)現(xiàn)部分(平臺(tái)實(shí)現(xiàn))分離,是他們都可以獨(dú)立地變化。
——《設(shè)計(jì)模式》GoF
橋模式的動(dòng)機(jī):
由于某些類型的固有的實(shí)現(xiàn)邏輯,使得它們具有兩個(gè)變化的維度,乃至多個(gè)維度的變化。
假設(shè)有如下的通信的偽碼描述包括登陸、發(fā)信息、播放音樂等等的功能,同時(shí)也支持不同的平臺(tái)的支持,比如PC、移動(dòng)端等,其中版本也具有不同的功能,比如精簡(jiǎn)版和完整版。
class Messager{
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;
virtual ~Messager(){}
};
//平臺(tái)實(shí)現(xiàn)
class PCMessagerBase : public Messager{
public:
virtual void PlaySound(){
//**********
}
virtual void DrawShape(){
//**********
}
virtual void WriteText(){
//**********
}
virtual void Connect(){
//**********
}
};
class MobileMessagerBase : public Messager{
public:
virtual void PlaySound(){
//==========
}
virtual void DrawShape(){
//==========
}
virtual void WriteText(){
//==========
}
virtual void Connect(){
//==========
}
};
//業(yè)務(wù)抽象
class PCMessagerLite : public PCMessagerBase {
public:
virtual void Login(string username, string password){
PCMessagerBase::Connect();
//........
}
virtual void SendMessage(string message){
PCMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image){
PCMessagerBase::DrawShape();
//........
}
};
class PCMessagerPerfect : public PCMessagerBase {
public:
virtual void Login(string username, string password){
PCMessagerBase::PlaySound();
//********
PCMessagerBase::Connect();
//........
}
virtual void SendMessage(string message){
PCMessagerBase::PlaySound();
//********
PCMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image){
PCMessagerBase::PlaySound();
//********
PCMessagerBase::DrawShape();
//........
}
};
class MobileMessagerLite : public MobileMessagerBase {
public:
virtual void Login(string username, string password){
MobileMessagerBase::Connect();
//........
}
virtual void SendMessage(string message){
MobileMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image){
MobileMessagerBase::DrawShape();
//........
}
};
class MobileMessagerPerfect : public MobileMessagerBase {
public:
virtual void Login(string username, string password){
MobileMessagerBase::PlaySound();
//********
MobileMessagerBase::Connect();
//........
}
virtual void SendMessage(string message){
MobileMessagerBase::PlaySound();
//********
MobileMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image){
MobileMessagerBase::PlaySound();
//********
MobileMessagerBase::DrawShape();
//........
}
};
void Process(){
//編譯時(shí)裝配
Messager *m =
new MobileMessagerPerfect();
}
對(duì)于以上那個(gè)代碼來說,組合的過程中會(huì)存在大量的重復(fù)代碼,同時(shí)在組合的過程中,會(huì)產(chǎn)生大量的類的組合而產(chǎn)生的類。
依靠Decorator的經(jīng)驗(yàn),把大量的繼承改編為持有原父類的指針變量,并且可以聲明為共同的父類,利用多態(tài)性將靜態(tài)綁定改為動(dòng)態(tài)綁定。
并且觀察發(fā)現(xiàn),對(duì)于Message類的功能應(yīng)該拆分開。
class Messager{
protected:
MessagerImp* messagerImp;//...
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;
virtual ~Messager(){}
};
class MessagerImp{
public:
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;
virtual MessagerImp(){}
};
//平臺(tái)實(shí)現(xiàn) n
class PCMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//**********
}
virtual void DrawShape(){
//**********
}
virtual void WriteText(){
//**********
}
virtual void Connect(){
//**********
}
};
class MobileMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//==========
}
virtual void DrawShape(){
//==========
}
virtual void WriteText(){
//==========
}
virtual void Connect(){
//==========
}
};
//業(yè)務(wù)抽象 m
//類的數(shù)目:1+n+m
class MessagerLite :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->Connect();
//........
}
virtual void SendMessage(string message){
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image){
messagerImp->DrawShape();
//........
}
};
class MessagerPerfect :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->PlaySound();
//********
messagerImp->Connect();
//........
}
virtual void SendMessage(string message){
messagerImp->PlaySound();
//********
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image){
messagerImp->PlaySound();
//********
messagerImp->DrawShape();
//........
}
};
void Process(){
//運(yùn)行時(shí)裝配
MessagerImp* mImp=new PCMessagerImp();
Messager *m =new Messager(mImp);
}
要點(diǎn)總結(jié):
Bridge模式使用“對(duì)象間的組合關(guān)系”解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系,使得抽象和實(shí)現(xiàn)可以沿著各自的維度來變化。所謂抽象和實(shí)現(xiàn)沿著各自維度的變化,即“子類化”他們。
Bridge模式有時(shí)候類似于多繼承方案,但是多繼承方案往往違背單一指責(zé)原則(即一個(gè)類只有一個(gè)變化的原因),復(fù)用性較差。Bridge模式是比多繼承方案更好的解決方法。
Bridge模式的應(yīng)用一般在“兩個(gè)非常強(qiáng)的變化維度”,有時(shí)一個(gè)類也有多于兩個(gè)的變化維度,這是可以使用Bridge的擴(kuò)展模式。