C++設計模式
為了理解松耦合設計思想,掌握面向對象設計原則
什么是設計模式?
是一種解決方案的核心,可以避免重復勞動
設計模式不等于面向對象設計模式
底層思維:向下,如何把握機器底層從微觀理解對象構造
語言構造,變易轉換
內存模型
運行時機制
抽象思維:向上,如何將我們的現實世界抽象為程序代碼,
面向對象
組件封裝
設計模式
架構模式
深入理解面向對象:
向下:封裝,繼承,多態
向上:深刻把握面向對象機制所帶來的抽象意義,理解如何利用這些機制來表達現實世界,掌握什么是好的面向對象設計
如何解決復雜性?
分解,人們面對復雜性有一個常見的做法:分而治之,將大問題分解為多個小問題,將復雜問題分解為多個簡單問題
抽象:更高層次來講,人們處理復雜性有一個通用的技術,由于不能掌握全部的復雜對象,我們選擇忽視它的非本質性細節而去處理泛化和理想化了的模型
class Point{
public:
? ? intx;
? ? int y;
};
class Line{
public:
? ? Pointstart;
? ? Pointend;
? ? Line(constPoint& start, const Point& end, ){
? ? ? ? This->start= start;
? ? ? ? This->end= end; ?
? ? }
};
抽象的過程
在shape里面有虛擬方法draw,一個形狀負責畫自己,實現自己的draw.
在子類中overide自己父類的虛函數
virtual void Draw(const Graphics& g){
g.DrawLine(Pens,Red, start.x, start.y, end.x, end.y);
}
class MainForm:public Form{
private:
pointp1;
point p2;
vector
shapeVector;//多態
public:
}
在后面對shapevector中的元素進行多態調用。
兩種方法的區別,哪種更好?
客戶需求的變化:
如果客戶需要多加一個圓
//增加一個類
class Circle{
};
在mainform里增加一個
vector CircleVector
如果檢測到要畫圓則要判斷將圓push——back到圓的vector里
然后刷新以后,要把圓顯示出來
用抽象的方法,建立新的circle類
class Circle: public shape{
public:
//負責自己的draw
}
vector不需要動,因為是shape*指針
全都不用改變除了刷新
工廠模式里在刷新一個圓的時候也不需要改變
重用性得到了很高的提升
當需求變化的時候,更加方便
DRY!!!
由于不能掌握全部的復雜對象,處理泛化的問題
面向對象的設計原則
變化是復用的天敵,面向對象設計最大的優勢在于抵御變化。
理解隔離變化
從宏觀層面來看,面向對象的構建方式更能適應軟件的變化,能將變化所帶來的影響減為最小。
各司其職
從微觀層面來看,面向對象的方式更強調個各類的責任
在第一個例子里,畫線的責任從mainform到了形狀自己,接口一樣但實際不一樣。
對象是什么?
從語言實現層面來看,對象封裝了代碼和數據,
從規格層面來看,對象是一系列可以被使用的公共接口
從概念層面來看,
面向對象的設計原則//設計原則比模式更重要
依賴倒置原則(DIP)
高層模塊(穩定)不應該依賴于低層模塊(變化),二者都應該依賴于抽象(穩定)
抽象(穩定)不應該依賴于實現細節(變化),實現細節應該依賴于抽象(穩定)。
開放封閉原則(OCP)
對擴展開放,對更改封閉
類模塊應該是可擴展的,但是不可以修改
單一職責原則(SRP)
一個類應該僅有一個引起它變化的原因
變化的方向隱含著類的責任
Liskov替換原則(LSP)
子類必須能夠替換它們的基類(IS-A)
繼承表達類型抽象
接口隔離原則(ISP)
不應該強迫客戶程序依賴它們不用的方法
接口應該小而完備
優先使用對象組合,而不是類繼承
類繼承通常為白箱復用,對象組合通常為黑箱復用。
繼承在某種程度上破壞了封裝性,子類父類耦合度高
而對象組合則只要求被組合的對象具有良好定義的接口,耦合度低。
封裝變化點
使用封裝來創建對象之間的分界層,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良影響。
針對接口編程,而不是針對實現編程。
不將變量類型聲明為具體的類,而是聲明為某個接口,客戶程序無需知道對象的具體類型,只需要知道對象所具有的接口。
減少系統中各部分的依賴關系,從而實現“高內聚,低耦合”類型的設計方案。
產業強盛的標志:接口標準化
模板方法
Template Method
GOF-23模式分類
設計模式的應用不應該先入為主,一上來就使用設計模式是對設計模式最大的誤用,沒有一步到位的設計模式。
重構的關鍵技法:
靜態到動態,早綁定到晚綁定,繼承到組合,編譯時依賴到運行時依賴,緊耦合到松耦合
組件協作模式:
框架與應用程序的劃分,組合協作模式通過晚期綁定,來實現框架和應用程序之間的松耦合,是二者之間協作時常用的模式。
典型模式:
template method
strategy
observer/event
動機:在軟件構件過程中,某項任務常常有穩定的整體操作結構,但各個子步驟卻有很多改變的需求,或者由于固有的原因,比如框架和應用之間的關系,而無法和任務的整體機構同時實現
class library{
public:
? ? voidstep1(){?
? ? //…
? ? }
? ? voidstep3(){
? ? //…
? ? }
? ? void step5(){
? ? }
};//程序庫開發人員
class Application{
? ? voidstep2{
? ? }
? ? void step4{
? ? }
};
int main(){
? ? Librarylib();
? ? Applicationapp();
? ? Lib.step1(); ??
? ? If(app.step2()){
? ? ? ? Lib.step3();
? ? }
….
}
另外一種做法:
庫的開發人員
除了1,3,5,也寫step2和step4
virtualbool step2(){}
virtualvoid step4(){}
把run()寫在類里,1,3,5是protected,虛的析構函數。
子類重寫實現
library* pLIb = new Apllication();
plib->run();
delete plib;
前一種方法lib開發人員開發1,3,5三個步驟,app開發人員開發2,4兩個步驟,和程序主流程
另一種lib開發人員寫1,3,5三個步驟和程序主流程,app開發人員開發2,4兩個步驟。
第一種是app調用lib的,第二種是lib的調用app的
第一種寫法是一種早綁定的寫法,因為lib一般寫的早,程序庫寫的早。晚的東西調用早的東西,但在面向對象語言中,有晚綁定的機制,lib寫的早但是它調用app,所以是晚綁定。模式定義一個操作算法的骨架(穩定),而將一些步驟延遲到子類中,template method是的子類可以不改變一個算法結構即可以重定義override,重寫該算法的某些特定步驟。
為什么叫template method?run就是一個樣板
穩定中有變化,2,4支持變化,是虛函數的多態調用
c++中穩定的要寫成非虛函數,變化的要寫成虛函數
設計模式的假定條件是必須有一個穩定點
也一定有變化,設計模式的最大的作用就是在穩定和變化之間尋找一個平衡點,把兔子關進籠子里。
在具體實現方面,被template method調用的虛方法可以具有實現,也可以沒有任何實現(抽象方法,虛方法),但一般推薦把它們設置為protected方法。
“不要調用我,讓我來調用你”的反向控制結構。
策略模式:
strategy策略模式是一個組件協作類的模式,實現框架和應用程序之間的松耦合
動機:在軟件構件過程中,有些對象使用的算法可能多種多樣經常改變,如果將這些算法都編碼到對象中,將會使對象變得復雜,有時候支持不適用的算法也是一個性能負擔,透明得更改,使算法和對象解耦。
Enum taxbase{
CN_Tax,
Us_tAX
dE_TAX
};
class SalesOrder{
? ? texbasetax;
public:
? ? doublecalculateTax(){
? ? if(tax== cn_tax){
? ? }
? ? else if(tax ==us_tax){
? ? }
? ? else if(tax==de_tax){
? ? }
}
};
有沒有可能未來支持其他國家的稅法
class taxStrategy{
public:
virtualdouble calculate(const context& context) = 0;
virtual~taxstrategy(){}
};
class CNTax:public taxstrategy{
public:
virtualdouble calculate(const context& context){}
};
class ustax: public taxstrategy{
public:
virtualdouble calculate(const context& context){}
};
…
class salesorder{
private:
taxstrategy*strategy;
public:
salesorder(strategyfactory*strategyfactory){
this->strategy= strategyfactory->newstrategy();
}
~salesorder(){}
public doublecalculatetax(){
contextcontext();
double val =
strategy->calculate(context);//多態調用
}
};
把一些列算法一個個封裝起來并且使他們可以互相替換,是算法獨立于客戶程序(穩定)而變化(擴展,子類化)
strategy使類型在運行時方便地根據需要在各個算法之間切換
strategy模式提供了用條件判斷語句以外的另一種選擇,消除條件判斷語句實在解耦合。
如果Strategy對象沒有實例變量,各個上下文可以共享一個strategy變量,從而節省對象開銷。
有很多ifelse代碼不會被真正使用但是要存在緩存里,使得有用的代碼被擠到主存里,但這個不是主要的好處。
Observer/event觀察者模式
動機:需要為某些對象建立一種通知依賴關系-一個對象的狀態發生改變,所有依賴對象(觀察者對象)都得到通知,如果這樣的依賴關系很緊密,不能很好地抵御變化。
Class mainform:public forms{
? ? Textbox*txtfilepath;
? ? Texbox*txtfilenumber;
Public:
? ? Voidbutton_click(){
? ? Stringfilepath = txtfilepath->gettext();
? ? Intnumber = atoi(txtfilenumber->gettext().c_str());
? ? Filesplittersplitter(filepath, number);
? ? Splitter.split();
}
};
class filesplitter{
stringm_filepath;
intm_filenumber;
public:
filesplitter(conststring& filepath, int filenumber):
{}
void split(){
//讀取大文件
//分批次向小文件中寫入
for(int I = 0; i
//…
}
}
};
需求是提供一個進度條,來展示完成進度
首先在maiform上有一個progressbar* progressbar成員
在file_splitter里也放一個progressbar
依賴:A依賴B,A在編譯的時候只有B存在才能通過。
編譯是依賴,
當我不需要用這個bar的時候會出問題,這個progressbar其實是一個通知。通知可以用抽象的方式來表達,而不是用一個控件
class IProgress{
public:
virtualvoid DoProgress(float value) = 0;
virtual~IProgress()
};
所以在filesplitter里就變成了
IProgress* m_Iprogress//抽象通知機制
If(m_Iprogress != nullptr){
M_Iprogress->DoProgress(i+1)/m_filenumber;
}
然后mainform多重繼承Iprogress,C++一般用到多重繼承都是一個基類和接口
裝飾模式:
Decorator裝飾模式
“單一職責模式”在軟件組件設計中,如果責任劃分不清晰,使用繼承得到的結果往往會讓子類急劇膨脹,同時充斥著重復代碼,這時候關鍵要劃清責任。
典型的模式有decorator和bridge。
Class stream{
Public:
? ? Virtualchar Read(int number) = 0;
? ? Virtualvoid seek(int position) = 0;
? ? Virtualvoid write(char data) = 0;
? ? Virtual~Stream(){}
};
class filestream : public stream{
};
class Networkstream: public stream{
};
我們需要對流的主體進行曹組偶以后才能加密。
Class CtyptoFileStream : public FileStream{
Public:
? ? Virtualchar Read(int number){
? ? FileStream:read(number);//讀文件流
}
};
緩沖操作
//額外的加密操作
//額外的緩沖操作
這個設計的問題
最頂上是stream,被filestream, networkstream和memeorystream三種繼承,然后每個分別有加密和緩沖的流
這樣流就有很多,但其中有重復的代碼
如何重構?
取消繼承,把父類當做一個對象放入類中
然后再把各個父類做成多態,用基類來表示,會發現所有的類全都一樣,只是運行的時候new出來的對象不一樣。
但是要保證流的方法是虛方法,所以要繼承自基類stream
橋模式:
由于某些類型的固有實現邏輯,使得他們有多個變化的維度
class messager(
public:
virtualvoid login
virtualvoid sendmessage
virtualvoid sendpicture
virtualvoid playsound
virtualvoid drawshape
virtualvoid writetext
virtual~message
);
我們還要支持PC平臺和mobile平臺的設計
class PCMessagerBase: public Messager{
public:
//重寫后面的幾個方法
};
class MobileMessagerBase: public Messager{
};
我們會發現在不同的平臺上要支持不同的功能
class PCMessageLite: public PCmessagerBase{
};
class PCMessagerPerfect: publicPCMessagerBase{
};
class MobileMessagerLite: publicMobileMessagerBase{
};
后面的類可以用messager的多態來實現,然后發現后面的lite和perfect并沒有重載前面的后面幾個方法,所以要把messager拆分開成兩個類。。
和decorator方法很像
如果子類里有重復的字段,都要往上提
abstraction和implementor,在abstraction里有一個implementor的指針,并且兩個東西分別有各自的子類,向兩個不同的方向延伸。