C++設(shè)計模式
為了理解松耦合設(shè)計思想,掌握面向?qū)ο笤O(shè)計原則
什么是設(shè)計模式等孵?
是一種解決方案的核心,可以避免重復(fù)勞動
設(shè)計模式不等于面向?qū)ο笤O(shè)計模式
底層思維:向下,如何把握機器底層從微觀理解對象構(gòu)造
語言構(gòu)造炫乓,變易轉(zhuǎn)換
內(nèi)存模型
運行時機制
抽象思維:向上,如何將我們的現(xiàn)實世界抽象為程序代碼砂豌,
面向?qū)ο?/p>
組件封裝
設(shè)計模式
架構(gòu)模式
深入理解面向?qū)ο螅?/p>
向下:封裝厢岂,繼承,多態(tài)
向上:深刻把握面向?qū)ο髾C制所帶來的抽象意義阳距,理解如何利用這些機制來表達現(xiàn)實世界塔粒,掌握什么是好的面向?qū)ο笤O(shè)計
如何解決復(fù)雜性?
分解筐摘,人們面對復(fù)雜性有一個常見的做法:分而治之卒茬,將大問題分解為多個小問題船老,將復(fù)雜問題分解為多個簡單問題
抽象:更高層次來講,人們處理復(fù)雜性有一個通用的技術(shù)圃酵,由于不能掌握全部的復(fù)雜對象柳畔,我們選擇忽視它的非本質(zhì)性細節(jié)而去處理泛化和理想化了的模型
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,一個形狀負責(zé)畫自己,實現(xiàn)自己的draw.
在子類中overide自己父類的虛函數(shù)
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;//多態(tài)
public:
}
在后面對shapevector中的元素進行多態(tài)調(diào)用郭赐。
兩種方法的區(qū)別薪韩,哪種更好?
客戶需求的變化:
如果客戶需要多加一個圓
//增加一個類
class Circle{
};
在mainform里增加一個
vector CircleVector
如果檢測到要畫圓則要判斷將圓push——back到圓的vector里
然后刷新以后捌锭,要把圓顯示出來
用抽象的方法俘陷,建立新的circle類
class Circle: public shape{
public:
//負責(zé)自己的draw
}
vector不需要動,因為是shape*指針
全都不用改變除了刷新
工廠模式里在刷新一個圓的時候也不需要改變
重用性得到了很高的提升
當(dāng)需求變化的時候观谦,更加方便
DRY@堋!豁状!
由于不能掌握全部的復(fù)雜對象捉偏,處理泛化的問題
面向?qū)ο蟮脑O(shè)計原則
變化是復(fù)用的天敵,面向?qū)ο笤O(shè)計最大的優(yōu)勢在于抵御變化泻红。
理解隔離變化
從宏觀層面來看夭禽,面向?qū)ο蟮臉?gòu)建方式更能適應(yīng)軟件的變化,能將變化所帶來的影響減為最小承桥。
各司其職
從微觀層面來看驻粟,面向?qū)ο蟮姆绞礁鼜娬{(diào)個各類的責(zé)任
在第一個例子里,畫線的責(zé)任從mainform到了形狀自己凶异,接口一樣但實際不一樣蜀撑。
對象是什么?
從語言實現(xiàn)層面來看剩彬,對象封裝了代碼和數(shù)據(jù)酷麦,
從規(guī)格層面來看,對象是一系列可以被使用的公共接口
從概念層面來看喉恋,
面向?qū)ο蟮脑O(shè)計原則//設(shè)計原則比模式更重要
依賴倒置原則(DIP)
高層模塊(穩(wěn)定)不應(yīng)該依賴于低層模塊(變化)沃饶,二者都應(yīng)該依賴于抽象(穩(wěn)定)
抽象(穩(wěn)定)不應(yīng)該依賴于實現(xiàn)細節(jié)(變化),實現(xiàn)細節(jié)應(yīng)該依賴于抽象(穩(wěn)定)轻黑。
開放封閉原則(OCP)
對擴展開放糊肤,對更改封閉
類模塊應(yīng)該是可擴展的,但是不可以修改
單一職責(zé)原則(SRP)
一個類應(yīng)該僅有一個引起它變化的原因
變化的方向隱含著類的責(zé)任
Liskov替換原則(LSP)
子類必須能夠替換它們的基類(IS-A)
繼承表達類型抽象
接口隔離原則(ISP)
不應(yīng)該強迫客戶程序依賴它們不用的方法
接口應(yīng)該小而完備
優(yōu)先使用對象組合氓鄙,而不是類繼承
類繼承通常為白箱復(fù)用馆揉,對象組合通常為黑箱復(fù)用。
繼承在某種程度上破壞了封裝性抖拦,子類父類耦合度高
而對象組合則只要求被組合的對象具有良好定義的接口升酣,耦合度低舷暮。
封裝變化點
使用封裝來創(chuàng)建對象之間的分界層,讓設(shè)計者可以在分界層的一側(cè)進行修改噩茄,而不會對另一側(cè)產(chǎn)生不良影響下面。
針對接口編程,而不是針對實現(xiàn)編程绩聘。
不將變量類型聲明為具體的類沥割,而是聲明為某個接口,客戶程序無需知道對象的具體類型凿菩,只需要知道對象所具有的接口驯遇。
減少系統(tǒng)中各部分的依賴關(guān)系,從而實現(xiàn)“高內(nèi)聚蓄髓,低耦合”類型的設(shè)計方案。
產(chǎn)業(yè)強盛的標(biāo)志:接口標(biāo)準(zhǔn)化
模板方法
Template Method
GOF-23模式分類
設(shè)計模式的應(yīng)用不應(yīng)該先入為主舒帮,一上來就使用設(shè)計模式是對設(shè)計模式最大的誤用会喝,沒有一步到位的設(shè)計模式。
重構(gòu)的關(guān)鍵技法:
靜態(tài)到動態(tài)玩郊,早綁定到晚綁定肢执,繼承到組合,編譯時依賴到運行時依賴译红,緊耦合到松耦合
組件協(xié)作模式:
框架與應(yīng)用程序的劃分预茄,組合協(xié)作模式通過晚期綁定,來實現(xiàn)框架和應(yīng)用程序之間的松耦合侦厚,是二者之間協(xié)作時常用的模式耻陕。
典型模式:
template method
strategy
observer/event
動機:在軟件構(gòu)件過程中,某項任務(wù)常常有穩(wěn)定的整體操作結(jié)構(gòu)刨沦,但各個子步驟卻有很多改變的需求诗宣,或者由于固有的原因,比如框架和應(yīng)用之間的關(guān)系想诅,而無法和任務(wù)的整體機構(gòu)同時實現(xiàn)
class library{
public:
? ? voidstep1(){?
? ? //…
? ? }
? ? voidstep3(){
? ? //…
? ? }
? ? void step5(){
? ? }
};//程序庫開發(fā)人員
class Application{
? ? voidstep2{
? ? }
? ? void step4{
? ? }
};
int main(){
? ? Librarylib();
? ? Applicationapp();
? ? Lib.step1(); ??
? ? If(app.step2()){
? ? ? ? Lib.step3();
? ? }
….
}
另外一種做法:
庫的開發(fā)人員
除了1召庞,3,5来破,也寫step2和step4
virtualbool step2(){}
virtualvoid step4(){}
把run()寫在類里篮灼,1,3徘禁,5是protected,虛的析構(gòu)函數(shù)诅诱。
子類重寫實現(xiàn)
library* pLIb = new Apllication();
plib->run();
delete plib;
前一種方法lib開發(fā)人員開發(fā)1,3晌坤,5三個步驟逢艘,app開發(fā)人員開發(fā)2旦袋,4兩個步驟,和程序主流程
另一種lib開發(fā)人員寫1它改,3疤孕,5三個步驟和程序主流程,app開發(fā)人員開發(fā)2央拖,4兩個步驟祭阀。
第一種是app調(diào)用lib的,第二種是lib的調(diào)用app的
第一種寫法是一種早綁定的寫法鲜戒,因為lib一般寫的早专控,程序庫寫的早。晚的東西調(diào)用早的東西遏餐,但在面向?qū)ο笳Z言中伦腐,有晚綁定的機制,lib寫的早但是它調(diào)用app失都,所以是晚綁定柏蘑。模式定義一個操作算法的骨架(穩(wěn)定),而將一些步驟延遲到子類中粹庞,template method是的子類可以不改變一個算法結(jié)構(gòu)即可以重定義override咳焚,重寫該算法的某些特定步驟。
為什么叫template method?run就是一個樣板
穩(wěn)定中有變化庞溜,2革半,4支持變化,是虛函數(shù)的多態(tài)調(diào)用
c++中穩(wěn)定的要寫成非虛函數(shù)流码,變化的要寫成虛函數(shù)
設(shè)計模式的假定條件是必須有一個穩(wěn)定點
也一定有變化又官,設(shè)計模式的最大的作用就是在穩(wěn)定和變化之間尋找一個平衡點,把兔子關(guān)進籠子里漫试。
在具體實現(xiàn)方面赏胚,被template method調(diào)用的虛方法可以具有實現(xiàn),也可以沒有任何實現(xiàn)(抽象方法商虐,虛方法)觉阅,但一般推薦把它們設(shè)置為protected方法。
“不要調(diào)用我秘车,讓我來調(diào)用你”的反向控制結(jié)構(gòu)典勇。
策略模式:
strategy策略模式是一個組件協(xié)作類的模式,實現(xiàn)框架和應(yīng)用程序之間的松耦合
動機:在軟件構(gòu)件過程中叮趴,有些對象使用的算法可能多種多樣經(jīng)常改變割笙,如果將這些算法都編碼到對象中,將會使對象變得復(fù)雜,有時候支持不適用的算法也是一個性能負擔(dān)伤溉,透明得更改般码,使算法和對象解耦。
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);//多態(tài)調(diào)用
}
};
把一些列算法一個個封裝起來并且使他們可以互相替換乱顾,是算法獨立于客戶程序(穩(wěn)定)而變化(擴展板祝,子類化)
strategy使類型在運行時方便地根據(jù)需要在各個算法之間切換
strategy模式提供了用條件判斷語句以外的另一種選擇,消除條件判斷語句實在解耦合走净。
如果Strategy對象沒有實例變量券时,各個上下文可以共享一個strategy變量,從而節(jié)省對象開銷伏伯。
有很多ifelse代碼不會被真正使用但是要存在緩存里橘洞,使得有用的代碼被擠到主存里,但這個不是主要的好處说搅。
Observer/event觀察者模式
動機:需要為某些對象建立一種通知依賴關(guān)系-一個對象的狀態(tài)發(fā)生改變炸枣,所有依賴對象(觀察者對象)都得到通知,如果這樣的依賴關(guān)系很緊密弄唧,不能很好地抵御變化抛虏。
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存在才能通過。
編譯是依賴慕淡,
當(dāng)我不需要用這個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裝飾模式
“單一職責(zé)模式”在軟件組件設(shè)計中,如果責(zé)任劃分不清晰携兵,使用繼承得到的結(jié)果往往會讓子類急劇膨脹疾掰,同時充斥著重復(fù)代碼,這時候關(guān)鍵要劃清責(zé)任徐紧。
典型的模式有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);//讀文件流
}
};
緩沖操作
//額外的加密操作
//額外的緩沖操作
這個設(shè)計的問題
最頂上是stream并级,被filestream, networkstream和memeorystream三種繼承拂檩,然后每個分別有加密和緩沖的流
這樣流就有很多,但其中有重復(fù)的代碼
如何重構(gòu)嘲碧?
取消繼承稻励,把父類當(dāng)做一個對象放入類中
然后再把各個父類做成多態(tài),用基類來表示愈涩,會發(fā)現(xiàn)所有的類全都一樣望抽,只是運行的時候new出來的對象不一樣加矛。
但是要保證流的方法是虛方法,所以要繼承自基類stream
橋模式:
由于某些類型的固有實現(xiàn)邏輯煤篙,使得他們有多個變化的維度
class messager(
public:
virtualvoid login
virtualvoid sendmessage
virtualvoid sendpicture
virtualvoid playsound
virtualvoid drawshape
virtualvoid writetext
virtual~message
);
我們還要支持PC平臺和mobile平臺的設(shè)計
class PCMessagerBase: public Messager{
public:
//重寫后面的幾個方法
};
class MobileMessagerBase: public Messager{
};
我們會發(fā)現(xiàn)在不同的平臺上要支持不同的功能
class PCMessageLite: public PCmessagerBase{
};
class PCMessagerPerfect: publicPCMessagerBase{
};
class MobileMessagerLite: publicMobileMessagerBase{
};
后面的類可以用messager的多態(tài)來實現(xiàn)斟览,然后發(fā)現(xiàn)后面的lite和perfect并沒有重載前面的后面幾個方法,所以要把messager拆分開成兩個類舰蟆。趣惠。
和decorator方法很像
如果子類里有重復(fù)的字段,都要往上提
abstraction和implementor身害,在abstraction里有一個implementor的指針味悄,并且兩個東西分別有各自的子類,向兩個不同的方向延伸塌鸯。