本周內(nèi)容
(1)設計模式簡介
(2)面向?qū)ο笤O計原則
(3)模板方法
(4)策略模式
(5)觀察者模式
(6)裝飾模式
(7)橋模式
一 設計模式簡介
- 課程目標
- 理解松耦合設計思想
- 掌握面向?qū)ο笤O計原則
- 掌握重構(gòu)技巧改善設計
- 掌握GOF 核心設計模式
什么是設計模式?每一個模式描述了一個在我們周圍不斷重復發(fā)生的問題,以及該問題的解決方案的核心。這樣渔工,你就能一次又一次地使用該方案而不必做重復勞動————Christopher Alexander的诵。
歷史性著作《設計模式:可復用面向?qū)ο筌浖幕A》一書中描述了種經(jīng)典面向?qū)ο笤O計模式职恳,創(chuàng)立了模式在軟件設計中的地位诬垂。由于《設計模式》一書確定了設計模式的地位稚机,通常所說的設計模式隱含地表示“面向?qū)ο笤O計模式”牍戚。但這并不意味“設計模式”就等于“面向?qū)ο笤O計模式”侮繁。
從面向?qū)ο笳勂?/p>
- 底層思維:向下,如何把握機器底層從微觀理解對象構(gòu)造
- 語言構(gòu)造
- 編譯轉(zhuǎn)換
- 內(nèi)存模型
- 運行時機制
- 抽象思維:向上如孝,如何將我們的周圍世界抽象為程序代碼
- 面向?qū)ο?/li>
- 組件封裝
- 設計模式
- 架構(gòu)模式
深入理解面向?qū)ο?/p>
- 向下:深入理解三大面向?qū)ο髾C制
- 封裝宪哩,隱藏內(nèi)部實現(xiàn)
- 繼承,復用現(xiàn)有代碼
- 多態(tài)第晰,改寫對象行為
- 向上:深刻把握面向?qū)ο髾C制所帶來的抽象意義锁孟,理解如何使用這些機制來表達現(xiàn)實世界彬祖,掌握什么是“好的面向?qū)ο笤O計”。
軟件設計復雜的根本原因:1.客戶需求的變化品抽;2.技術(shù)平臺的變化储笑;3.開發(fā)團隊的變化;4.市場環(huán)境的變化圆恤。所以代碼的復用性對于效率的提升是非常行之有效的突倍。
通常解決復雜性有兩種方式(對這兩種方式更深的理解詳見Mypain代碼例子)
- 分解:人們面對復雜性有一個常見的做法:即分而治之豁跑,將大問題分解為多個小問題狼牺,將復雜問題分解為多個簡單問題褥实。
- 抽象:更高層次來講捌蚊,人們處理復雜性有一個通用的技術(shù),即抽象依痊。由于不能掌握全部的復雜對象簸州,我們選擇忽視它的非本質(zhì)細節(jié)缠借,而去處理泛化和理想化了的對象模型拆火。
二 面向?qū)ο笤O計原則
- 理解隔離變化
- 從宏觀層面來看跳夭,面向?qū)ο蟮臉?gòu)建方式更能適應軟件的變化涂圆,能將變化所帶來的影響減為最小
- 各司其職
- 從微觀層面來看们镜,面向?qū)ο蟮姆绞礁鼜娬{(diào)各個類的“負責”
- 由于需求變化導致的新增類型不應該影響原來類型的實現(xiàn)——是所謂各負其責
- 對象是什么?
- 從語言實現(xiàn)層面來看润歉,對象封裝了代碼和數(shù)據(jù)模狭。
- 從規(guī)格層面講,對象是一系列可被使用的公共接口踩衩。
- 從概念層面講嚼鹉,對象是某種擁有責任的抽象。
兩個非常相似的代碼可能設計模式完全不一樣驱富,兩個相差千里的代碼可能設計模式是類似的锚赤。
設計原則一:實例代碼中第一種Mainform依賴Line和Rect,這是不好的褐鸥,第二種Mainform依賴一個抽象Shape线脚,Line和Rect依賴Shape,實現(xiàn)隔離變化叫榕。
- 依賴倒置原則(DIP)
- 高層模塊(穩(wěn)定)不應該依賴于低層模塊(變化)浑侥,二者都應該依賴于抽象(穩(wěn)定)。
- 抽象(穩(wěn)定)不應該依賴于實現(xiàn)細節(jié)(變化)晰绎,實現(xiàn)細節(jié)應該依賴于抽象(穩(wěn)定)寓落。
設計原則二:比如某公司制造了一批桌子,后來顧客有防火等級的需求荞下,笨的做法是把這些桌子扔掉伶选,聰明的辦法是在桌子上涂上防火材料史飞。
- 開放封閉原則(OCP)
- 對擴展開放,對更改封閉考蕾。
- 類模塊應該是可拓展的祸憋,但是不可修改。
設計原則三:
- 單一職責原則(SRP)
- 一個類應該僅有一個引起它變化的原因肖卧。
- 變化的方向隱含著類的責任蚯窥。
設計原則四:
- Liskov替換原則(LSP)
- 子類必須能夠替換它們的基類(IS-A)。
- 繼承表達類型抽象塞帐。
設計原則五:只是子類使用protected拦赠,本類使用private,真正有需要就public葵姥。
- 接口隔離原則(ISP)
- 不應該強迫客戶程序依賴它們不用的方法荷鼠。
- 接口應該小而完備。
設計原則六:
- 優(yōu)先使用對象組合榔幸,而不是類繼承
- 類繼承通常為“白箱復用”允乐,對象組合通常為“黑箱復用”。
- 繼承在某種程度上破壞了封裝性削咆,子類父類耦合度高牍疏。
- 而對象組合則只要求被組合的對象具有良好定義的接口,耦合度低拨齐。
設計原則七:一般層次對封裝的理解是封裝數(shù)據(jù)和代碼鳞陨,更高層次的理解是封裝變化層,一側(cè)穩(wěn)定瞻惋,一側(cè)不穩(wěn)定厦滤。
- 封裝變化點:
- 使用封裝來創(chuàng)建對象之間的分界層,讓設計者可以在分界層的一側(cè)進行修改歼狼,而不會對另一側(cè)產(chǎn)生不良的影響掏导,從而實現(xiàn)層次間的松耦合。
設計原則八
- 針對接口編程羽峰,而不是針對實現(xiàn)編程
- 不將變量類型聲明為某個特定的具體類趟咆,而是聲明為某個接口。
- 客戶程序無需獲知對象的具體類型限寞,只需要知道對象所具有的接口忍啸。
- 減少系統(tǒng)類型中各部分的依賴關系,從而實現(xiàn)“高內(nèi)聚履植、松耦合”的類型設計方案计雌。
將設計原則提升為設計經(jīng)驗
- 設計習語Design Idioms
- Design Idioms描述與特定編程語言相關的低層模式,技巧玫霎,慣用法凿滤。
- 設計模式Design Patterns
- Design Patterns主要描述的是“類與相互通信的對象之間的組織關系妈橄,包括它們的角色、職責翁脆、協(xié)作方式等方面眷蚓。
- 架構(gòu)模式Architectural Patterns
- Architectural Patterns描述系統(tǒng)中與基本結(jié)構(gòu)組織關系密切的高層模式,包括子系統(tǒng)劃分反番,職責沙热,以及如何組織它們之間關系的規(guī)則。
- GOF-23模板分類
- 從目的來看:
- 創(chuàng)建型(Creational)模式:將對象的部分創(chuàng)建工作延遲到子類或者其他對象罢缸,從而應對需求變化為對象創(chuàng)建時具體類型實現(xiàn)引來的沖擊篙贸。
- 結(jié)構(gòu)型(Structural)模式:通過類繼承或者對象組合獲得更靈活的結(jié)構(gòu),從而應對需求變化為對象的結(jié)構(gòu)帶來的沖擊枫疆。
- 行為型(Behavioral)模式:通過類繼承或者對象組合來劃分類與對象間的職責爵川,從而應對需求變化為多個交互的對象帶來的沖擊。
- 從范圍來看:
- 類模式處理類與子類的靜態(tài)關系
- 對象模式處理對象間的動態(tài)關系
- 重構(gòu)獲得模式Refactoring to Patterns
- 面向?qū)ο笤O計模式是“好的面向?qū)ο笤O計”息楔,所謂“好的面向?qū)ο笤O計”指是那些可以滿足“應對變化寝贡,提高復用”的設計。
- 現(xiàn)代軟件設計的特征是“需求的頻繁變化”值依。設計模式的要點是“尋求變化點圃泡,然后在變化點處應用設計模式,從而來更好地應對需求的變化”鳞滨《幢海“什么時候蟆淀、什么地點應用設計模式”比“理解設計模式結(jié)構(gòu)本身”更為重要拯啦。
- 設計模式的應用不宜先入為主,一上來就使用設計模式是對設計模式的最大誤用熔任。沒有一步到位的設計模式褒链。敏捷軟件開發(fā)實踐提倡的“Refactoring to Patterns”是目前普遍公認的最好的使用設計模式的方法。
重構(gòu)關鍵技法
- 靜態(tài)->動態(tài)
- 早綁定->晚綁定
- 繼承->組合
- 編譯時依賴->運行時依賴
- 緊耦合->松耦合
三 模板方法
- “組件協(xié)作”模式:現(xiàn)代軟件專業(yè)分工之后的第一個結(jié)果是“框架與應用程序的劃分”疑苔,“組件協(xié)作”模式通過晚期綁定甫匹,來實現(xiàn)框架與應用程序之間的松耦合,是二者之間協(xié)作時常用的模式惦费。典型模式:Template Method兵迅、Strategy、Observer/Event
動機
- 在軟件構(gòu)建過程中薪贫,對于某一項任務恍箭,它常常有穩(wěn)定的整體操作結(jié)構(gòu),但各個子步驟卻有很多改變的需求瞧省,或者由于固定的原因(比如框架與應用之間的關系)而無法和任務的整體結(jié)構(gòu)同時實現(xiàn)扯夭。
- 如何在確定穩(wěn)定操作結(jié)構(gòu)的前提下鳍贾,來靈活應對各個子步驟的變化或者晚期實現(xiàn)需求?
基類析構(gòu)函數(shù)要寫成虛析構(gòu)函數(shù)交洗,如果不寫虛的骑科,主程序delete的時候可能調(diào)用不到子類的析構(gòu)函數(shù)。
早綁定:后寫的調(diào)用先寫的
晚綁定:先寫的調(diào)用后寫的所有都是穩(wěn)定或者不穩(wěn)定都不適用設計模式构拳,設計模式最大的作用就是在變化和穩(wěn)定中間尋找隔離點咆爽,然后來分離他們,從而來管理變化置森,生動的例子:把變化的讓它像小兔子一樣關在籠子里在房間里跳不至于把房間污染掉伍掀。
模式定義:定義一個操作中的算法的骨架(穩(wěn)定),而將一些步驟延遲(變化)到子類中暇藏。Template Method使得子類可以不改變(復用)一個算法的結(jié)構(gòu)即可重定義(override重寫)該算法的某些特定步驟蜜笤。——《設計模式》GoF
對比兩種設計方式:
方式一結(jié)構(gòu)化軟件設計流程
//程序庫開發(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();
}
方式二面向?qū)ο筌浖O計流程
//應用程序開發(fā)人員
class Application : public Library {
protected:
virtual bool Step2(){
//... 子類重寫實現(xiàn)
}
virtual void Step4() {
//... 子類重寫實現(xiàn)
}
};
int main()
{
Library* pLib=new Application();
lib->Run();
delete pLib;
}
}
//程序庫開發(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; //變化
};
第二種設計方式代碼的復用性比第一種高盐碱。
結(jié)構(gòu)(Structure):TemplateMethod()是穩(wěn)定的把兔,PrimitiveOperation1()和PrimitiveOperation2()是變化的
要點總結(jié)
- Template Method模式是一種非常基礎性的設計模式瓮顽,在面向?qū)ο笙到y(tǒng)中有著大量的應用县好。它用最簡潔的機制(虛函數(shù)的多態(tài)性)為很多應用程序框架提供了靈活的擴展點,是代碼復用(二進制運行時刻的復用暖混,不是代碼的復用)方面的基本實現(xiàn)結(jié)構(gòu)缕贡。
- 除了可以靈活應對子步驟的變化外,“不要調(diào)用我拣播,讓我來調(diào)用你”的反向控制結(jié)構(gòu)是Template Method的典型應用晾咪。
- 在具體實現(xiàn)方面,被Template Method調(diào)用的虛方法可以具有實現(xiàn)贮配,也可以沒有任何實現(xiàn)(抽象方法谍倦、純虛方法),但一般推薦將它們設置為protected方法泪勒。
四 策略模式
動機
在軟件構(gòu)建過程中昼蛀,某些對象使用的算法可能多種多樣,經(jīng)常改變圆存,如果將這些算法都編碼到對象中叼旋,將會使對象變得異常復雜;而且有時候支持不使用的算法也是一個性能負擔沦辙。
如何在運行時根據(jù)需要透明地更改對象的算法夫植?將算法與對象本身解耦,從而避免上述問題怕轿?
模式定義
定義一系列算法偷崩,把它們一個個封裝起來辟拷,并且使它們可互相替換(變化)。該模式使得算法可獨立于使用它的客戶程序(穩(wěn)定)而變化(擴展阐斜,子類化)衫冻。————《設計模式》GoF
對比傳統(tǒng)if-else方式和策略模式
//if-else方式
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){ //更改
//...
}
//....
}
};
//策略模式
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)調(diào)用
//...
}
};
第一種if-else方式違反了開放封閉原則谒出,擴展需要更改源代碼隅俘,代碼復用性不高,而第二種策略模式則很好地優(yōu)化這點笤喳,但是當情況是固定的比如一周有七天为居,每天都干什么,則可使用if-else方式
杀狡。
策略模式結(jié)構(gòu)圖:Context和Strategy是穩(wěn)定的蒙畴,ConcreteStrategyA、ConcreteStrategyB呜象、ConcreteStrategyC是變化的膳凝。
要點總結(jié)
- Strategy及其子類為組件提供了一系列可重用的算法,從而可以使得類型在運行時方便地根據(jù)需要在各個算法之間進行切換恭陡。
- Strategy模式提供了用條件判斷語句以外的另一種選擇蹬音,消除條件判斷語句,就是在解耦合休玩。含有許多條件判斷語句的代碼通常都需要Strategy模式著淆。
- 如果Strategy對象沒有實例變量,那么各個上下文可以共享同一個Strategy對象拴疤,從而節(jié)省對象開銷永部。
五 觀察者模式
動機
- 在軟件構(gòu)建過程中,我們需要為某些對象建立一種“通知依賴關系”————一個對象(目標對象)的狀態(tài)發(fā)生改變遥赚,所有的依賴對象(觀察者對象)都將得到通知扬舒。如果這樣的依賴關系過于緊密阐肤,將使軟件不能很好地抵御變化凫佛。
- 使用面向?qū)ο蠹夹g(shù),可以將這種依賴關系弱化孕惜,并形成一種穩(wěn)定的依賴關系愧薛。從而實現(xiàn)軟件體系結(jié)構(gòu)的松耦合。
模式定義:
定義對象間的一種一對多(變化)的依賴關系衫画,以便當一個對象(Subject)的狀態(tài)發(fā)生改變時毫炉,所有依賴于它的對象都得到通知并自動更新∠髡郑————設計模式GoF
- 要點總結(jié):
- 使用面向?qū)ο蟮某橄竺楣矗琌bserver模式使得我們可以獨立地改變目標與觀察者费奸,從而使二者之間的依賴關系達致松耦合。
- 目標發(fā)送通知時进陡,無需指定觀察者愿阐,通知(可以攜帶通知信息作為參數(shù))會自動傳播。
- 觀察者自己決定是否需要訂閱通知趾疚,目標對象對此一無所知缨历。
- Observer模式是基于事情的UI框架中非常常用的設計模式,也是MVC模式的一個重要組成部分糙麦。
六 裝飾模式
- “單一職責”模式:
- 在軟件組件的設計中辛孵,如果責任劃分的不清晰,使用繼承得到的結(jié)果往往是隨著需求的變化赡磅,子類急劇膨脹魄缚,同時充斥著重復代碼,這時候的關鍵是劃清責任焚廊。典型模式:Decorator鲜滩、Bridge。
動機
- 在某些情況下我們可能會“過度地使用繼承來擴展對象的功能”节值,由于繼承為類型引入的靜態(tài)特質(zhì)徙硅,使得這種擴展方式缺乏靈活性;并且隨著子類的增多(擴展功能的增多)搞疗,各種子類的組合(擴展功能的組合)會導致更多子類的膨脹嗓蘑。
- 何如使“對象功能的擴展”能夠根據(jù)需要來動態(tài)地實現(xiàn)?同時避免“擴展功能的增多”帶來的子類膨脹問題匿乃?從而使得任何“功能擴展變化”所導致的影響降為最低桩皿?
模式定義:
動態(tài)(組合)地給一個對象增加一些額外的職責。就增加功能而言幢炸,Decorator模式比生成子類(繼承)更為靈活(消除重復代碼&減少子類個數(shù))泄隔。————《設計模式》GoF
要點總結(jié):
- 通過采用組合而非繼承的手法宛徊,Decorator模式實現(xiàn)了在運行時動態(tài)擴展對象功能的能力佛嬉,而且可以根據(jù)需要擴展多個功能。避免了使用繼承帶來的“靈活性差”和“多子類衍生問題”闸天。
- Decorator類在接口上表現(xiàn)為is-a Component的繼承關系暖呕,即Decorator類繼承了Component類所具有的接口。但在實現(xiàn)上又表現(xiàn)為has-a Component的組合關系苞氮,即Decorator類又使用了另外一個Component類湾揽。
- Decorator模式的目的并非解決“多子類衍生的多繼承”問題,Decorator模式應用的要點在于解決“主體類在多個方向上的擴展功能”——是為“裝飾”的含義。
七 橋模式
動機
- 由于某些類型的固有的實現(xiàn)邏輯库物,使得它們具有兩個變化的維度霸旗,乃至多個維度的變化。
- 如何應對這種“多維度的變化”戚揭?如何利用面對對象技術(shù)來使得類型可以輕松地沿著兩個乃至多個方向變化定硝,而不引入額外的復雜度?
模式定義:
將抽象部分(業(yè)務功能)與實現(xiàn)部分(平臺實現(xiàn))分離毫目,使它們都可以獨立地變化蔬啡。——《設計模式》GoF
要點總結(jié):
- Bridge模式使用“對象間的組合關系”解耦了抽象和實現(xiàn)之間固有的綁定關系镀虐,使得抽象和實現(xiàn)可以沿著各自的維度來變化箱蟆。所謂抽象和實現(xiàn)沿著各自維度的變化,即“子類化”它們刮便。
- Bridge模式有時候類似于多繼承方案空猜,但是多繼承方案往往違背單一職責原則(即一個類只有一個變化的原因),復用性比較差恨旱。Bridge模式是比多繼承方案更好的解決方法辈毯。
- Bridge模式的應用一般在“兩個非常強的變化維度”,有時一個類也有多于兩個的變化維度搜贤,這時可以使用Bridge的擴展模式谆沃。(如果有三個變化維度,把其他變化維度的合在一起打包成一個基類仪芒,用三個抽象的指針指向它唁影。)