(1)設計模式簡介
以下代碼能夠實現對圖形的繪制:
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){
//針對直線
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);
}
//針對矩形
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);
}
上述代碼體現了一種分而治之的思想,在MainForm的類里面對顯示界面的每個形狀都進行了維護和處理沧烈。
在上述代碼中只是對于矩形、直線和點三種形式的圖形進行了繪制方式的設定妄均,若此時想繪制一個圓竟坛,此時代碼不能提供相對應的方法,這也就意味著代碼將會發(fā)生變化框冀。
需增加一個圓的類class Circle,同時還需要進行如下更改:
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){
//針對直線
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);
}
//針對矩形
for (int i = 0; i < rectVector.size(); i++){
e.Graphics.DrawRectangle(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
//改變
//針對圓形
for (int i = 0; i < circleVector.size(); i++){
e.Graphics.DrawCircle(Pens.Red,
circleVector[i]);
}
//...
Form::OnPaint(e);
}
上述修改的代碼段比較分散敏簿,如果站在軟件工程的角度上來看明也,對于修改過的部分都需要在重新測試等一些列的操作,其實對于功能擴充來說其實面對著后期大量的工作惯裕。
關于抽象的設計思路:
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;
}
//實現自己的Draw温数,負責畫自己
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;
}
//實現自己的Draw,負責畫自己
virtual void Draw(const Graphics& g){
g.DrawRectangle(Pens.Red,
leftUp,width,height);
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
//針對所有形狀
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){
//針對所有形狀
for (int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); //多態(tài)調用蜻势,各負其責
}
//...
Form::OnPaint(e);
}
對于每個形狀撑刺,都增加了一個共同的父類Shape,同時對于圖形來說應該如何繪制自己的這樣的形狀的函數都是由自己來實現的(Draw()函數)握玛。MainForm只需要管理Shape的指針够傍,而不再具體管理某一個具體的形狀,實現了一次向上抽象的管理挠铲。同時onPaint的方法來說冕屯,也不在直接管理繪制的問題,而是調用了Shape中的虛函數Draw拂苹,通過多態(tài)調用來實現了MainForm來調用了自己繪制自己的方法安聘。
此時若再次需要添加一個對圓形的處理,則其偽碼如下:
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;
}
//實現自己的Draw瓢棒,負責畫自己
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;
}
//實現自己的Draw浴韭,負責畫自己
virtual void Draw(const Graphics& g){
g.DrawRectangle(Pens.Red,
leftUp,width,height);
}
};
//增加
class Circle : public Shape{
public:
//實現自己的Draw,負責畫自己
virtual void Draw(const Graphics& g){
g.DrawCircle(Pens.Red,
...);
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
//針對所有形狀
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){
//針對所有形狀
for (int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); //多態(tài)調用脯宿,各負其責
}
//...
Form::OnPaint(e);
}
以上可以看出解決了之前代碼所存在問題念颈,由于這樣的抽象的思想,使得代碼的變更更加容易嗅绰,重用性得到了提升舍肠。
什么是好的軟件設計呢搀继?軟件設計的金科玉律:復用。
變化是復用最大的天敵翠语!面向對象的最大優(yōu)勢就在于:抵御變化叽躯。
(2)面向對象設計原則
<1>依賴倒置原則(DIP)
1)高層模塊(穩(wěn)定的)不應該依賴于低層模塊(容易變化的),二者都應該依賴于抽象(穩(wěn)定的);
2)抽象(穩(wěn)定的)不應該依賴于實現細節(jié)(容易變化的)肌括,實現細節(jié)應該依賴于抽象(穩(wěn)定的)点骑。
<2>開放封閉原則(OCP)
1)對擴展開放,對更改封閉谍夭;
2)模塊應該是可擴展的矗烛,但是不可修改。
<3>單一指責原則(SRP)
1)一個類應該僅有一個引起它變化的原因涕烧;
2)變化的方向隱含著類的責任区赵。
<4>Liskov替換原則(LSP)
1)子類必須能夠替換他們的基類(is-a);
2)集成表達抽象類型珠漂。
<5>接口隔離原則(ISP)
1)不應該強迫客戶程序依賴他們不用的方法晚缩;
2)接口應該小而完備。
<6>優(yōu)先使用對象組合媳危,而不是類繼承
1)類繼承通常為“白盒復用”荞彼,對象組合通常為“黑箱復用”;
2)繼承在某種程度上破壞了封裝性待笑,子類父類耦合度高鸣皂;
3)而對象組合則只要求被組合的對象具有良好定義的接口,耦合度低暮蹂。
<7>封裝變化點
使用封裝來創(chuàng)建對象之間的分界層寞缝,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良的影響椎侠,從而實現層次間的松耦合第租。
<8>針對接口編程,而不是針對實現編程
1)不講變量類型聲明為某個特定的具體類我纪,而是聲明為某個接口慎宾;
2)客戶程序無需獲知對象的具體類型,只需要知道對象所具有的接口浅悉;
3)減少系統(tǒng)中個部分的依賴關系趟据,從而實現“高內聚、松耦合”的類型設計方案术健。
產業(yè)強盛的標志:接口的標準化
GOF-23模式分類方式:
<1>依據目的分類
1)創(chuàng)建型(Creational)模式
2)結構型(Structural)模式
3)行為型(Behavioral)模式
<2>依據范圍分類
1)類模式處理與子類的靜態(tài)關系
2)對象模式處理間的動態(tài)關系
(3)模板方法
定一個操作中的算法的骨架(穩(wěn)定的)汹碱,而將一些步驟延遲(容易變化的)到子類中。Template Method使得子類可以不改變(復用)一個算法的結構即可重新定義(Override覆寫)該算法的某寫特定步驟
——《設計模式(GoF)》
對于庫的開發(fā)人員來說荞估,已經開發(fā)完成了所需要功能的1咳促、3稚新、5三個步驟;而如果要實現這個完整功能跪腹,需要應用程序的開發(fā)人員實現其中的2和4這兩步褂删。同時還需要應用程序的開發(fā)者在使用的時候依次來調用庫和自己開發(fā)的這幾個函數。
//程序庫開發(fā)人員
class Library{
public:
void Step1(){
//...
}
void Step3(){
//...
}
void Step5(){
//...
}
};
//應用程序開發(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>對應用程序的開發(fā)人員來說冲茸,他需要自己開發(fā)其中的第二和第五步的函數的開發(fā)屯阀。對于應用程序的開發(fā)者來說,要求是比較高的轴术,必須對庫中的函數情況比較了解难衰,重寫的兩個函數的難度也相對較大,對于函數整體執(zhí)行流程也不被庫函數的開發(fā)人員所控制逗栽。
<2>庫開發(fā)人員和應用程序的開發(fā)人員所開發(fā)的內容的耦合度很高盖袭,彼此相互交織在一起,還需要由用開發(fā)人員來組織整體調用流程彼宠。未來程序的擴展性和可維護性的難度都比較大苍凛。
通過模板方法可以解決以上問題:
//程序庫開發(fā)人員
class Library{
public:
//穩(wěn)定 template method
void Run(){
Step1();
if (Step2()) { //支持變化 ==> 虛函數的多態(tài)調用
Step3();
}
for (int i = 0; i < 4; i++){
Step4(); //支持變化 ==> 虛函數的多態(tài)調用
}
Step5();
}
virtual ~Library(){ }
protected:
void Step1() { //穩(wěn)定
//.....
}
void Step3() {//穩(wěn)定
//.....
}
void Step5() { //穩(wěn)定
//.....
}
virtual bool Step2() = 0;//變化
virtual void Step4() =0; //變化
};
//應用程序開發(fā)人員
class Application : public Library {
protected:
virtual bool Step2(){
//... 子類重寫實現
}
virtual void Step4() {
//... 子類重寫實現
}
};
int main()
{
Library* pLib=new Application();
lib->Run();
delete pLib;
}
}
能夠開發(fā)庫的開發(fā)人員能增加了兩個虛函數,同時庫的開發(fā)人員定義了一個run方法兵志,在run方法中,按照步驟調用了按照規(guī)則來調用的幾個方法宣肚。
此時應用程序的開發(fā)人員只需要重寫庫函數中定義的函數想罕。這樣不僅避免了可能出現的缺陷,同時對于應用程序來說使用庫的流程變得極其簡單霉涨,只需要調用run方法即可按价,而不需要考慮具體的調用流程和規(guī)則。
對于Template Method來說笙瑟,有一個前提楼镐,就是Run()方法必須要是穩(wěn)定的。如果Run()不穩(wěn)定往枷,那么沒有一個穩(wěn)定的軟件的骨架框产,就不存在這樣一種設計模式。假定错洁,所有方式都是穩(wěn)定秉宿,那么其實也沒有必要使用設計模式。
Template Method是一種非惩筒辏基礎性的設計模式描睦,在面向對象的系統(tǒng)中有著大量的應用。他用最簡潔的機制(虛函數的多態(tài)性)為很多應用程序的框架提供了靈活的擴展點导而,是代碼復用方面的基本實現結構忱叭。
除了可以靈活對應子步驟的變化外隔崎,“不要調用我,讓我來調用你”的反向控制結構是Template Method的典型應用韵丑。
在具體實現方面爵卒,被Template Method調用的虛方法可以具有實現,也可以沒有任何實現(抽象方法埂息、純虛方法)技潘,一般推薦將他們設置為protected方法。
(4)策略模式
定義一系列算法千康,把他們一個個封裝起來享幽,并且是他們可以互相替換(變化)。該模式似的算法可以獨立于使用它的客戶程序(穩(wěn)定的)而變化(擴展拾弃,子類化)值桩。
——《設計模式》GoF
策略模式的動機為:在軟件構建的過程中,某些對象使用的算法可能是多種多樣的豪椿,經常改變奔坟,如果將這些算法都編碼到對象中,將會使對象變得異常復雜搭盾;而且有時候支持不使用的算法也是一種性能負擔咳秉。
比如當前有一種稅種的邏輯處理機制:
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***********
}
//....
}
};
若此時需要增加一種稅種:
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中的參數,還需要在SalseOrder中添加關于新增加稅種的一個判斷鸯隅,然后才能進行相應的計算澜建。此時違反了對開閉原則,同時也違反了反向依賴的原則蝌以。
使用策略模式:
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)調用
//...
}
};
此時再增加一種稅種:
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 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)調用
//...
}
};
SalesOrder的CalculateTax函數多態(tài)調用了TaxStrategy類中的Calucate方法炕舵。而TaxStrategy的每個子類都是一種新的稅種計算方式,每個稅種計算中還實現了自己對應的稅種計算的方法跟畅。
在擴展的時候咽筋,需要增加TaxStrategy的子類就行了,不需要在改變SalesOrder的中的代碼了徊件。
總結:
<1>Strategy及其子類為組件提供了一系列可重用的算法奸攻,從而可以使得類型在運行時方便的根據需要在各個算法之間進行切換。
<2>Strategy模式提供了用條件判斷語句的另一種選擇庇忌,消除條件判斷語句舞箍,就是在解耦合。含有許多條件判斷語句的代碼通常都需要Strategy模式皆疹。尤其是條件判斷語句在未來會有增加可能性的時候疏橄,應該優(yōu)先考慮Strategy模式。
<3>如果Strategy對象沒有實例變量,那么各個上下文可以共享同一個Strategy對象捎迫,從而節(jié)省對象的開銷晃酒。
(5)觀察者模式
觀察者模式的動機:
在軟件構建的過程中,我們需要為某些對象建立一種“通知依賴關系”——一個對象(目標對象)的狀態(tài)發(fā)生改變窄绒,所有的以來對象(觀察者對象)都將得到通知贝次。如果這樣的依賴關系過于緊密,將使得軟件不能很好的抵御變化彰导。
使用面向對象技術蛔翅,可以將這種依賴關系弱化,并形成一種穩(wěn)定的依賴關系位谋。從而實現軟件體系結構的松耦合山析。
以下是一個文件分割器的偽碼,其中存在一個進度條:
class FileSplitter
{
string m_filePath;
int m_fileNumber;
ProgressBar* m_progressBar; // 產生編譯式依賴掏父,實際是一個通知
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++){
//...
// 更新進度條數據
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();
}
};
在以上的設計方法上笋轨,前端界面依賴了一個ProgressBar,是一種實現細節(jié)赊淑,而實現細節(jié)是極易變化的爵政。同時其中的耦合性也很高,只要需要更改顯示的進度的情況陶缺,就需要更改前端的界面钾挟。
由以上的依賴關系不難看出,對于分隔文件的算法相對來說穩(wěn)定饱岸,但是卻依賴了一個不穩(wěn)定的ProgressBar等龙。
那么如果對于界面修改的部分來說,他面對的是一個通知的數組伶贰,每個數組里面都是一個抽象的通知的接口,就可以使用多態(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; // 抽象通知機制黍衙,支持多個觀察者
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); //更新進度條
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 << ".";
}
};
對于這個解決方案來說荠诬,將ProgressBar進行了抽象琅翻,對于FileSplitter只需要依賴一個list,由一個list來維護IProgressBar* 的列表柑贞,對于每個ProgressBar控件來說方椎,只需要繼承IProgressBar即可。
對于MainForm來說钧嘶,只需要調用比較穩(wěn)定的FileSplitter既可以來調用其中的分隔文件的方法棠众,而在FileSplitter中已經實現了遍歷list中所有IProgressBar并通知的功能,因此實現了觀察者模式的要求。
要點總結:
使用面向對象的抽象闸拿,Observer模式使得我們可以獨立地改變目標與觀察者空盼,從而使二者之間的依賴關系達到松耦合。目標發(fā)送通知時新荤,無需制定觀察者揽趾,通知(可以攜帶通知信息作為參數)會自動傳播。觀察者自己決定是否需要訂閱通知苛骨,目標對象對此一無所知篱瞎。Observer模式是基于事件的UI框架中非常常用的設計模式,也是MVC模式的一個重要組成部分痒芝。
(6)裝飾模式
動態(tài)(組合)地給一個對象增加一些額外的指責俐筋。就增加功能而言,Decorator模式比生成子類(繼承)更為靈活(消除重復代碼吼野、減少子類個數)校哎。
——《設計模式》GoF
裝飾模式動機:
在某些情況下,我們可能會“過度的使用繼承來擴展對象的功能”瞳步,由于繼承為類型引入靜態(tài)特質闷哆,使得這種擴展方式缺乏靈活性;并且隨著子類的增多(擴展功能的在增多)单起,各種子類的組合(擴展功能的組合)會導致子類的膨脹抱怔。
在下面有一組偽代碼,這組偽代碼主要描述一些流的操作嘀倒,比如文件流屈留、網絡流、內存流等测蘑,如果我們對于流提出更多的要求灌危,比如需要對文件流加密、對網絡流加密以及內存流的加密碳胳,同時如果考慮緩沖的情況下勇蝙,那么這些流都可能會要緩沖,也可能繼續(xù)的組合挨约,產生加密緩沖網絡流等等的操作味混。
//業(yè)務操作
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){
//讀網絡流
}
virtual void Seek(int position){
//定位網絡流
}
virtual void Write(char data){
//寫網絡流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//讀內存流
}
virtual void Seek(int position){
//定位內存流
}
virtual void Write(char data){
//寫內存流
}
};
//擴展操作
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);//讀網絡流
}
virtual void Seek(int position){
//額外的加密操作...
NetworkStream::Seek(position);//定位網絡流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
NetworkStream::Write(data);//寫網絡流
//額外的加密操作...
}
};
class CryptoMemoryStream : public MemoryStream{
public:
virtual char Read(int number){
//額外的加密操作...
MemoryStream::Read(number);//讀內存流
}
virtual void Seek(int position){
//額外的加密操作...
MemoryStream::Seek(position);//定位內存流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
MemoryStream::Write(data);//寫內存流
//額外的加密操作...
}
};
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(){
//編譯時裝配
CryptoFileStream *fs1 = new CryptoFileStream();
BufferedFileStream *fs2 = new BufferedFileStream();
CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}
上述代碼的復用性不高,對于每種方式都有大量的重復代碼诫惭。這樣的設計會導致class的數量激增翁锡。
如果把那些因為組合而產生的class(比如CryptoFileStream)的繼承關系先打斷,而是改為使用內部持有原先父類的指針夕土,比如CryptoFileStream原先繼承了FileStream馆衔,改為持有FileStream。這時候原class中的調用父類的函數全都可以改為使用自己持有的指針對象來調用。此時代碼的重復性大幅度提高哈踱,只是每一個組合型的class中持有的那個指針不屬于同一個(比如荒适,持有的為FileStream,NetworkStream以及BufferStream的指針)开镣,如果觀察一下刀诬,也不難發(fā)現,其實FileStream邪财,NetworkStream以及BufferStream又都有同一個父類Stream陕壹,所以利用多態(tài)性,就可以將每個組合類型中的持有的指針變量树埠,聲明為他們共同的父類糠馆,利用多態(tài)性只需要讓父類的指向子類即可(
Stream stream = new FileStream();
),這就實現了怎憋,編譯時都為一樣的又碌,在運行時提供不同的對象。此時會發(fā)現具有大量重復的類绊袋,合并了同類項毕匀,減少很多不必要的組合類。
//業(yè)務操作
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){
//讀網絡流
}
virtual void Seek(int position){
//定位網絡流
}
virtual void Write(char data){
//寫網絡流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//讀內存流
}
virtual void Seek(int position){
//定位內存流
}
virtual void Write(char data){
//寫內存流
}
};
//擴展操作
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(){
//運行時裝配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}
觀察上面的代碼來說癌别,其實有很多class里面都持有同樣的變量stream皂岔,那么這些類可以向上再抽象一個父類,將strem 在父類中聲明展姐。
//業(yè)務操作
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){
//讀網絡流
}
virtual void Seek(int position){
//定位網絡流
}
virtual void Write(char data){
//寫網絡流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//讀內存流
}
virtual void Seek(int position){
//定位內存流
}
virtual void Write(char data){
//寫內存流
}
};
//擴展操作
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(){
//運行時裝配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}
FileStream躁垛、NetworkStream還有BufferStream可以獨立執(zhí)行任務,而加密和緩存其實就是對他們的增強圾笨。只需要對Stream(這三個類的共同父類)增加加密功能教馆、增加緩沖的功能,而在使用的時候自由組合即可擂达,而不需要把代碼全都寫出來活玲。
總結:
<1>通過采用組合而非繼承的手法,Decorator模式實現了在運動時動態(tài)擴展對象功能的能力谍婉,而且可以根據需要擴展多個功能。避免了使用集成帶來的“靈活性差”和“多子類衍生的問題”镀钓。
<2>Decorator類在接口上變現為is-a Component的繼承關系穗熬,即Decorator類集成了Component類所具有的接口。但在實現上又表現為has-a Component的組合關系丁溅,即Decorator類又使用了另外一個Component類唤蔗。
<3>Decorator模式的目的并非解決“多子類衍生的多繼承”問題,Decorator模式應用的重要點在于解決“主體類在多個方向上的擴展功能”——是為“裝飾”的含義。
(7)橋模式
將抽象部分(業(yè)務功能)與實現部分(平臺實現)分離妓柜,是他們都可以獨立地變化箱季。
——《設計模式》GoF
橋模式的動機:
由于某些類型的固有的實現邏輯,使得它們具有兩個變化的維度棍掐,乃至多個維度的變化藏雏。
假設有如下的通信的偽碼描述包括登陸、發(fā)信息作煌、播放音樂等等的功能掘殴,同時也支持不同的平臺的支持,比如PC粟誓、移動端等奏寨,其中版本也具有不同的功能,比如精簡版和完整版鹰服。
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(){}
};
//平臺實現
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è)務抽象
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(){
//編譯時裝配
Messager *m =
new MobileMessagerPerfect();
}
對于以上那個代碼來說病瞳,組合的過程中會存在大量的重復代碼,同時在組合的過程中悲酷,會產生大量的類的組合而產生的類套菜。
依靠Decorator的經驗,把大量的繼承改編為持有原父類的指針變量舔涎,并且可以聲明為共同的父類笼踩,利用多態(tài)性將靜態(tài)綁定改為動態(tài)綁定。
并且觀察發(fā)現亡嫌,對于Message類的功能應該拆分開嚎于。
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(){}
};
//平臺實現 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è)務抽象 m
//類的數目: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(){
//運行時裝配
MessagerImp* mImp=new PCMessagerImp();
Messager *m =new Messager(mImp);
}
要點總結:
Bridge模式使用“對象間的組合關系”解耦了抽象和實現之間固有的綁定關系,使得抽象和實現可以沿著各自的維度來變化挟冠。所謂抽象和實現沿著各自維度的變化于购,即“子類化”他們。
Bridge模式有時候類似于多繼承方案知染,但是多繼承方案往往違背單一指責原則(即一個類只有一個變化的原因)肋僧,復用性較差。Bridge模式是比多繼承方案更好的解決方法控淡。
Bridge模式的應用一般在“兩個非常強的變化維度”嫌吠,有時一個類也有多于兩個的變化維度,這是可以使用Bridge的擴展模式掺炭。