課程目標(biāo)
1.理解松耦合設(shè)計(jì)思想
2.掌握面向?qū)ο笤O(shè)計(jì)原則
3.掌握重構(gòu)技法改善設(shè)計(jì)
4.掌握GOF核心設(shè)計(jì)模式
設(shè)計(jì)模式
描述重復(fù)發(fā)生的問(wèn)題以及該問(wèn)題解決方案的核心全跨。
面向?qū)ο驩OP
向下:
封裝——隱藏內(nèi)部實(shí)現(xiàn)
繼承——復(fù)用現(xiàn)有代碼
多態(tài)——改寫(xiě)對(duì)象行為
向上:將真實(shí)世界夏伊,抽象為程序代碼(好厲害的樣子绪抛,黑客帝國(guó)袁串?-?)
軟件設(shè)計(jì)很復(fù)雜痢甘,有人為原因宪拥,也有代碼原因。
解決方案:
分解——大----星蕴!卡啰;復(fù)雜---簡(jiǎn)單。
抽象——保留本質(zhì),刪除細(xì)節(jié),模型化族阅。
軟件設(shè)計(jì)的目標(biāo):復(fù)用
重新認(rèn)識(shí)OOP
1.理解隔離變化
架構(gòu)的系統(tǒng)能適應(yīng)新變化,將變化范圍減到最小。
2.各司其職
新增類型不應(yīng)該影響原來(lái)類型的實(shí)現(xiàn)般卑。
3.對(duì)象的含義
語(yǔ)言實(shí)現(xiàn)-----對(duì)象即封裝的代碼和數(shù)據(jù)
規(guī)格層面-----對(duì)象即一系列可被使用的公共接口
概念層面-----對(duì)象即擁有某種責(zé)任的抽象
面向?qū)ο笤O(shè)計(jì)原則(抵御變化酿秸,將變化范圍降到最泄j)加班少~~
1.依賴倒置原則(DIP)
2.開(kāi)放封閉原則(OCP)
3.單一職責(zé)原則(SRP)
4.Liskov替換原則(LSP)
·子類必須能夠替換他們的基類(IS-A)及穗。
·繼承表達(dá)類型抽象摧茴。
5.接口隔離原則(ISP)
·接口小 而完備。
·不強(qiáng)迫客戶程序依賴他們不用的方法(簡(jiǎn)單直接)
6.優(yōu)先使用對(duì)象組合埂陆,而不是類繼承
7.封裝變化點(diǎn)
8.針對(duì)接口編程苛白,而不是針對(duì)實(shí)現(xiàn)編程
·將變量類型聲明為某個(gè)接口
·客戶程序只需知道對(duì)象具有的接口
·減少系統(tǒng)中各部分的依賴關(guān)系,實(shí)現(xiàn)“高內(nèi)聚焚虱,松耦合”的類型設(shè)計(jì)方案
面向接口設(shè)計(jì)(接口標(biāo)準(zhǔn)化)
GOF-23模式分類
目的分類:
---------------------創(chuàng)建型模式-----------------------
單例模式:保證創(chuàng)建的一個(gè)對(duì)象(類)只有一個(gè)例购裙。
原型模式:要?jiǎng)?chuàng)建和原型相同的新對(duì)象
工廠方法:用于創(chuàng)建對(duì)象的接口,來(lái)讓子類決定實(shí)例化哪一個(gè)類鹃栽。
抽象工廠:提供一個(gè)創(chuàng)建一系列或相關(guān)依賴對(duì)象的接口躏率,而無(wú)需指定它們具體的類。
建造者:創(chuàng)建對(duì)象時(shí)民鼓,將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離薇芝,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。
--------------------結(jié)構(gòu)型模式------------------------
適配器:將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口丰嘉。
外觀:為子系統(tǒng)中的一組接口提供一個(gè)一致的界面夯到,外觀模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用饮亏。
代理:為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)耍贾。
裝飾:動(dòng)態(tài)的給一個(gè)對(duì)象添加一些額外的職責(zé)。
享元:運(yùn)用共享技術(shù)路幸,有效的支持大量細(xì)粒度的對(duì)象荐开。
組合:將對(duì)象組合成樹(shù)形結(jié)構(gòu)以表示部分-整體的層次結(jié)構(gòu),組合模式使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性
橋接:將抽象部分與它的實(shí)現(xiàn)部分分離劝赔,使他們獨(dú)立變化誓焦。
----------------------行為型模式--------------------
觀察者:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系胆敞,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí)着帽,所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
模板:定義一個(gè)操作的算法骨架移层,而將一些步驟延遲到子類中仍翰,模板方法是的子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
命令:將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象观话,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化予借;可以對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可撤銷的操作。
狀態(tài):允許一個(gè)對(duì)象再起內(nèi)部狀態(tài)改變時(shí)改變他的行為灵迫,讓對(duì)象看起來(lái)似乎修改了它的類秦叛。
職責(zé)鏈:使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接受者之間的耦合關(guān)系瀑粥。將這些對(duì)象連城一條鏈挣跋,并沿著這條鏈傳遞該請(qǐng)求,直到有一個(gè)對(duì)象處理它為止狞换。
解釋器:給定一個(gè)語(yǔ)言避咆,定義它的文法的一種表示,并定義一個(gè)解釋器修噪,這個(gè)解釋器使用該表示來(lái)解釋語(yǔ)言中的句子查库。
中介者:用一個(gè)中介對(duì)象來(lái)封裝一些列的對(duì)象交互。中介者使各對(duì)象不需要顯式的相互引用黄琼,從而使其耦合樊销,而且可以獨(dú)立的改變它們之間的交互。
訪問(wèn)者:表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作脏款。它使你可以再不改變各元素的類的前提下定義作用于這些元素的新操作现柠。
策略:定義一些列的算法,把他們一個(gè)個(gè)封裝起來(lái)弛矛,并且使它們可以相互替換够吩。本模式使得算法可獨(dú)立于使用它的客戶而變化。
備忘錄:再不破壞封裝的前提下丈氓,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài)周循,并在該對(duì)象之外保持這個(gè)狀態(tài)。這樣以后就可將該對(duì)象恢復(fù)到原先保存的狀態(tài)万俗。
迭代器:提供一種方法順序訪問(wèn)一個(gè)聚合對(duì)象中各個(gè)元素湾笛,而又不需要暴露該對(duì)象的內(nèi)部表示。
范圍分類
----------------------類模式(處理類與子類)--------靜態(tài)關(guān)系----------------------
具體未知…………(以后補(bǔ)充)
----------------------對(duì)象模式(處理對(duì)象)-----------動(dòng)態(tài)關(guān)系----------------------
具體未知…………(以后補(bǔ)充)
封裝變化分類
---------------------組件協(xié)作--------------------
模板:Template Method
策略:Strategy
觀察者/事件:Observer/Event
---------------------單一職責(zé)--------------------
裝飾:Decorator
橋接:Bridge
---------------------對(duì)象創(chuàng)建--------------------
工廠:Factory Method
抽象工廠:Abstract Factory
原型:Prototype
建造者:Builder
---------------------對(duì)象性能--------------------
單例:Singleton
享元:Flyweight
---------------------接口隔離--------------------
外觀:Facade
代理:Proxy
中介者:Mediator
適配器:Adapter
---------------------狀態(tài)變化--------------------
備忘錄:Memento
狀態(tài):State
---------------------數(shù)據(jù)結(jié)構(gòu)--------------------
組合:Composite
迭代器:Iterator
職責(zé)鏈:Chain of Responsibility
---------------------行為變化--------------------
命令:Command
訪問(wèn)者:Visitor
---------------------領(lǐng)域問(wèn)題--------------------
解釋器:Interpreter
重構(gòu)關(guān)鍵技法
靜態(tài)---------->動(dòng)態(tài)
早綁定------->晚綁定
繼承---------->組合
編譯時(shí)依賴-->運(yùn)行時(shí)依賴
緊耦合------->松耦合
——————————————————————————————————————————————————
模板:Template Method
定義:
一個(gè)操作的算法骨架闰歪,而將一些步驟延遲到子類中嚎研,模板方法是的子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
舉例:把動(dòng)物放在冰箱的步驟1库倘、打開(kāi)冰箱門(mén)临扮,2、放進(jìn)去教翩,3杆勇、關(guān)上冰箱門(mén)。(Template Method)但每個(gè)步驟針對(duì)不同的情況饱亿,都有不一樣的做法蚜退,這就要看你隨機(jī)應(yīng)變啦(具體實(shí)現(xiàn)闰靴,重現(xiàn)特定步驟);
代碼演示:
//程序庫(kù)開(kāi)發(fā)人員
//template_lib1.cpp
class Library{
public:
void Step1(){
//...
}
void Step3(){
//...
}
void Step5(){
//...
}
};
//應(yīng)用程序開(kāi)發(fā)人員
//template_app1.cpp
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();
}
//程序庫(kù)開(kāi)發(fā)人員
//template_lib1.cpp
class Library{
public:
//穩(wěn)定 template method
//穩(wěn)定中有變化钻注,很多設(shè)計(jì)模式的代碼結(jié)構(gòu)
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)用程序開(kāi)發(fā)人員
//template_app1.cpp
class Application : public Library {
protected:
virtual bool Step2(){
//... 子類重寫(xiě)實(shí)現(xiàn)
}
virtual void Step4() {
//... 子類重寫(xiě)實(shí)現(xiàn)
}
};
int main()
{
Library* pLib=new Application();
lib->Run();
delete pLib;
}
}
要點(diǎn)總結(jié)
1.非陈烨遥基礎(chǔ)的設(shè)計(jì)模式,在OOP中被被大量應(yīng)用幅恋。它用最簡(jiǎn)潔的機(jī)制(虛函數(shù)的多態(tài)性)為很多應(yīng)用程序框架提供了靈活的擴(kuò)展點(diǎn)膘掰。是代碼復(fù)用方面的基本實(shí)現(xiàn)結(jié)構(gòu)。
2.除了可靈活應(yīng)對(duì)子步驟變化外佳遣,“不要調(diào)用我识埋,讓我來(lái)調(diào)用你”的反向控制結(jié)構(gòu)是Template Method的典型應(yīng)用
3.在具體實(shí)現(xiàn)方面,被Template Method調(diào)用的虛方法可以有具體實(shí)現(xiàn)零渐,也可以沒(méi)有任何實(shí)現(xiàn)(抽象方法窒舟、純虛方法),但一般推薦將他們?cè)O(shè)置為protected.诵盼。
策略:Strategy
動(dòng)機(jī):
在軟件構(gòu)建過(guò)程中惠豺,某些對(duì)象使用的算法可能多種多樣,經(jīng)常改變风宁,如果將這些算法都編碼到對(duì)象中洁墙,將會(huì)使對(duì)象變得異常復(fù)雜,而且有時(shí)候支持不使用或不常使用的算法也是一個(gè)性能負(fù)擔(dān)戒财。
如何在運(yùn)行時(shí)根據(jù)需要透明地改變對(duì)象的算法热监?將算法和對(duì)象本身解耦,從而避免上述問(wèn)題饮寞?
定義:
定義一系列算法孝扛, 把它們一個(gè)個(gè)封裝起來(lái),并且使它們可以互相替換(變化)幽崩。該模式使得算法可獨(dú)立于使用它的客戶程序(穩(wěn)定)而變化(擴(kuò)展苦始,子類化)。
第一種方法:
如果使用第一種方法慌申,需要更改枚舉類型和if else語(yǔ)句(第6行和24行)陌选,違背了開(kāi)放封閉原則(對(duì)擴(kuò)展開(kāi)發(fā),對(duì)修改封閉)蹄溉;
// strategy1.cpp
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){ //更改
//...
}
//....
}
第二種方法:
采用第二種方法(策略模式)咨油,定義稅法基類,對(duì)不同國(guó)家稅法定義不同子類类缤,override Calculate方法臼勉。59行邻吭,多態(tài)調(diào)用餐弱。
假設(shè)出現(xiàn)變化:擴(kuò)展32 - 39行,SalesOrder不變(復(fù)用性,遵循開(kāi)放封閉原則)膏蚓。
補(bǔ):面向?qū)ο筇岬降膹?fù)用性瓢谢,指的是編譯單位(二進(jìn)制單位)的復(fù)用性,不是粘貼代碼的復(fù)用性
//Strategy.cpp
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)用
//...
}
};
要點(diǎn)總結(jié):
Strategy及其子類為組件提供了一系列可重用的算法驮瞧,從而可以得到類型在運(yùn)行時(shí)方便地根據(jù)需要在各個(gè)算法之間進(jìn)行切換氓扛。
Strtegy模式提供了用條件判斷語(yǔ)句以外的另一種選擇消除條件判斷語(yǔ)句,就是在解耦合论笔。含有許多條件判斷語(yǔ)句的代碼通常都需要Strategy模式采郎。(除非if else語(yǔ)句絕對(duì)不變,如對(duì)一周七天判斷等)
如果Strategy對(duì)象沒(méi)有實(shí)例變量狂魔,那么各個(gè)上下午可以共享同一個(gè)Strategy對(duì)象蒜埋,從而節(jié)省對(duì)象開(kāi)銷。
觀察者/事件:Observer/Event
定義:
定義對(duì)象間的一種一對(duì)多(變化)的依賴關(guān)系最楷,以便當(dāng)一個(gè)對(duì)象(Subject)的狀態(tài)發(fā)生改變時(shí)整份,所有依賴于它的對(duì)象都得到通知并自動(dòng)更新。
第一種方法:
分析代碼:違背依賴倒置原則
第6行: ProgressBar作為實(shí)現(xiàn)細(xì)節(jié)(表現(xiàn)形式可以多樣變化籽孙,如圖形界面烈评,數(shù)字顯示等)。其扮演的角色和任務(wù)是通知犯建。
將通知的任務(wù)不用控件方式(太過(guò)細(xì)節(jié))實(shí)現(xiàn)讲冠。
//FileSplitter1.cpp
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.分批次向小文件中寫(xiě)入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue);
}
}
// MainForm1.cpp
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();
}
};
第二種方法:(重構(gòu)第一種)
添加 IProgress類,作為抽象的通知機(jī)制适瓦,即觀察者(訂閱者)沟启。觀察者內(nèi)含有更新方法DoProgress()。具體觀察者(MainForm/ConsoleNotifier)繼承該類犹菇,實(shí)現(xiàn)各自更新功能德迹。
FileSplitter作為被觀察對(duì)象(對(duì)象/發(fā)布者),其中含有抽象通知機(jī)制IProgress指針揭芍,利用DoProgress()方法進(jìn)行通知胳搞。
即對(duì)象(發(fā)布者)不必考慮觀察者情況,自動(dòng)進(jìn)行通知(發(fā)布)工作称杨;
觀察者(訂閱者)根據(jù)自身實(shí)際情況選擇是否訂閱或如何處理通知肌毅。
同時(shí)考慮多個(gè)觀察者問(wèn)題添加List<IProgress*>。
//FileSplitter2.cpp
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.分批次向小文件中寫(xiě)入
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++;
}
}
};
//MainForm2.cpp
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 << ".";
}
};
Observer: IProgress; Update():DoProgress() .
ConcreteSubject: FileSplitter
ConcreteObserver: Mainform / ConsoleNotifier
根據(jù)標(biāo)準(zhǔn)的Observer模式定義锭汛,也可將addIProgress笨奠,removeProgress袭蝗,onProgress單獨(dú)定義類(Subject),再將FileSplitter(ConcreteSubject)繼承此類般婆。
上述方法2 相當(dāng)于將類圖中的Subject 和 ConcreteSubject合二為一到腥。
要點(diǎn)總結(jié):
1.使用面向?qū)ο蟮某橄螅琌bserver模式使得我們可以獨(dú)立地改變目標(biāo)與觀察者蔚袍,從而使二者之間的依賴關(guān)系達(dá)到松耦合乡范。
2.目標(biāo)發(fā)送通知時(shí),無(wú)需指定觀察者啤咽,通知會(huì)(可以攜帶通知信息作為參數(shù))自動(dòng)傳播晋辆。
onProgress(progressValue);//發(fā)送通知,無(wú)需考慮具體的觀察者問(wèn)題宇整。
3.觀察者自己決定是否訂閱通知栈拖,目標(biāo)對(duì)象對(duì)此一無(wú)所知。
79 splitter.addIProgress(this); //訂閱通知
80 splitter.addIProgress(&cn)没陡; //訂閱通知
4.Observer模式是基于事件的UI框架中非常常用的設(shè)計(jì)模式涩哟,也是MVC模式的一個(gè)重要組成部分。
---------------------單一職責(zé)--------------------
在軟件組件設(shè)計(jì)中盼玄,如果責(zé)任劃分的不清晰贴彼,使用繼承得到的結(jié)果,往往會(huì)隨著需求變化埃儿,子類急劇膨脹器仗,同時(shí)伴隨代碼重復(fù)。這個(gè)時(shí)候最重要的是劃清責(zé)任童番。
裝飾:Decorator
橋接:Bridge
---------------------
裝飾:Decorator
動(dòng)機(jī):
某些情況我們可能會(huì)過(guò)度使用繼承來(lái)擴(kuò)展對(duì)象功能精钮,由于繼承為類型引入了靜態(tài)特質(zhì),使得這種擴(kuò)展方式缺乏靈活性剃斧,并且隨著子類(擴(kuò)展功能)的增多轨香,各種子類(擴(kuò)展功能)的組合會(huì)導(dǎo)致更多子類的膨脹。
如何使(對(duì)象功能的擴(kuò)展)根據(jù)需要?jiǎng)討B(tài)實(shí)現(xiàn)幼东?
同時(shí)避免(擴(kuò)展功能的增多)帶來(lái)的子類膨脹問(wèn)題臂容?
將任何(功能擴(kuò)展變化)帶來(lái)的影響降為最低?————————裝飾模式:Decorator
定義:
動(dòng)態(tài)(組合)的給一個(gè)對(duì)象增加一些額外的職責(zé)根蟹。就增加功能而言脓杉,Decorator模式比生成子類(繼承)更為靈活(消除重復(fù)代碼,減少子類個(gè)數(shù)简逮。)
結(jié)構(gòu):
舉例:
比如有一個(gè)手機(jī)球散,允許你為手機(jī)添加特性,比如增加掛件散庶、屏幕貼膜等蕉堰。一種靈活的設(shè)計(jì)方式是凌净, 將手機(jī)嵌入到另一對(duì)象中,由這個(gè)對(duì)象完成特性的添加嘁灯,我們稱這個(gè)嵌入的對(duì)象為裝飾泻蚊。這個(gè)裝飾與它所裝飾的組件接口一致躲舌,因此它對(duì)使用該組件的客戶透明丑婿。
要點(diǎn)總結(jié)
1.通過(guò)采用組合并非繼承的手法,Decorator模式實(shí)現(xiàn)了在運(yùn)行時(shí)動(dòng)態(tài)擴(kuò)展對(duì)象功能的能力没卸,而且可以根據(jù)需要擴(kuò)展多個(gè)功能羹奉。避免了使用繼承帶來(lái)的”靈活性差“和”多子類衍生問(wèn)題
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模式的目的并非解決”多字類衍生的多繼承“問(wèn)題煤蚌,Decorator模式應(yīng)用的要點(diǎn)在于解決”主體類在多個(gè)方向上的擴(kuò)展功能“(顯然file,network與加密耕挨,緩沖是兩種擴(kuò)展方向) ——是為”裝飾“的含義。
代碼示例:
不同的流操作(文件流尉桩,網(wǎng)絡(luò)流筒占,內(nèi)存流)及其擴(kuò)展功能(加密,緩沖)等的實(shí)現(xiàn)
代碼1
數(shù)據(jù)規(guī)模: 假設(shè)有n種文件蜘犁,m種功能操作翰苫。該實(shí)現(xiàn)方法有(1 + n + n * m! / 2) 數(shù)量級(jí)的子類;
同時(shí)考察59行这橙,79行奏窑,98行本身是相同的代碼(類似還有很多),存在大量的冗余和重復(fù)屈扎。
開(kāi)始重構(gòu)埃唯,見(jiàn)方法2.
//Decorator1.cpp
//業(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){
//寫(xiě)文件流
}
};
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){
//寫(xiě)網(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){
//寫(xiě)內(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);//寫(xiě)文件流
//額外的加密操作...
}
};
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);//寫(xiě)網(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);//寫(xiě)內(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);//寫(xiě)文件流
//額外的加密操作...
//額外的緩沖操作...
}
};
void Process(){
//編譯時(shí)裝配
CryptoFileStream *fs1 = new CryptoFileStream();
BufferedFileStream *fs2 = new BufferedFileStream();
CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}
代碼2
針對(duì)上述代碼,重構(gòu)步驟如下:
1.考察 CryptoFileStream 鹰晨,CryptoNetworkStream筑凫,CryptoMemoryStream三個(gè)類,將其繼承FileStream并村,NetworkStream巍实,NetworkStream改為組合;即
class CryptoFileStream{
FileStream* stream;
public:
virtual char Read(int number){
//額外的加密操作...
stream -> Read(number);//改用字段方式調(diào)用Read()
// ...seek() write() 同理
}
}
class CryptoNetworkStream{
NetworkStream* stream;
public:
virtual char Read(int number){
//額外的加密操作...
stream -> Read(number);//改用字段方式調(diào)用Read()
//... seek() write() 同理
}
}
class CryptoMemoryStream{
MemoryStream* stream;
public:
virtual char Read(int number){
//額外的加密操作...
stream -> Read(number);//改用字段方式調(diào)用Read()
//... seek() write() 同理
}
}
2.考察上述2行哩牍, 13行棚潦, 24行, 發(fā)現(xiàn)其均為Stream子類膝昆, 應(yīng)使用多態(tài)性繼續(xù)重構(gòu)丸边。
class CryptoFileStream{
Stream* stream; // = new FileStream()
public:
virtual char Read(int number){
//額外的加密操作...
stream -> Read(number);//改用字段方式調(diào)用Read()
// ...seek() write() 同理
}
}
class CryptoNetworkStream{
Stream* stream; // = new NetworkStream();
public:
virtual char Read(int number){
//額外的加密操作...
stream -> Read(number);//改用字段方式調(diào)用Read()
//... seek() write() 同理
}
}
class CryptoMemoryStream{
Stream* stream; // = newMemoryStream()
public:
virtual char Read(int number){
//額外的加密操作...
stream -> Read(number);//改用字段方式調(diào)用Read()
//... seek() write() 同理
}
}
3.發(fā)現(xiàn)三個(gè)類是相同的叠必,不同的實(shí)現(xiàn)(需求的變化)是在運(yùn)行時(shí)實(shí)現(xiàn),編譯時(shí)復(fù)用妹窖,改為一個(gè)類即可纬朝,命名為CryptoStream。
同時(shí)為了保證接口規(guī)范(read,seek等仍然是虛函數(shù))骄呼,繼承Stream,出現(xiàn)既有組合共苛,又有繼承的情況。
class CryptoStream : public Stream{
Stream* stream; // = new ...
public:
virtual char Read(int number){
//額外的加密操作...
stream -> Read(number);//改用字段方式調(diào)用Read()
// ...seek() write() 同理
}
}
4.添加相應(yīng)構(gòu)造器蜓萄,得到此輪重構(gòu)后的結(jié)果隅茎,代碼如下,主要查看使用方式(運(yùn)行時(shí)裝配):
//Decorator2.cpp
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){
//寫(xiě)文件流
}
};
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){
//寫(xiě)網(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){
//寫(xiě)內(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);//寫(xiě)文件流
//額外的加密操作...
}
};
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);
}
代碼3
上述實(shí)現(xiàn)代碼2已經(jīng)極大地緩解了冗余問(wèn)題嫉沽,符合面向?qū)ο蟮脑O(shè)計(jì)思想辟犀,該輪重構(gòu)是錦上添花。
重構(gòu)步驟如下:
考察上述代碼绸硕,多個(gè)子類都有同樣的字段(Stream* stream;//...)
應(yīng)考慮“往上提”堂竟,方法有兩種,第一種是提到基類(顯然不合適玻佩,F(xiàn)ileStream等并不需要Stream字段 )
所以考慮第二種方法出嘹,實(shí)現(xiàn)一個(gè)“中間類”。
DecoratorStream: public Stream{
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm){
}
};
CryptoStream等繼承中間類DecoratorStream:
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
//...
}
重構(gòu)完成的最終版本:
FileStream,NetworkStream,MemoryStream等可以創(chuàng)建各自的對(duì)象夺蛇;
但實(shí)現(xiàn)加密疚漆,緩存功能必須在已有FileStream/NetworkStream等對(duì)象基礎(chǔ)上;
這些操作本質(zhì)是擴(kuò)展操作刁赦,也就是“裝飾”的含義娶聘。
此時(shí)類圖示意:
這時(shí)類的數(shù)量為(1 + n + 1 + m)
//Decorator3.cpp
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){
//寫(xiě)文件流
}
};
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){
//寫(xiě)網(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){
//寫(xiě)內(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);//寫(xiě)文件流
//額外的加密操作...
}
};
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);
}
橋接:Bridge
動(dòng)機(jī):
由于某些類型的固有的實(shí)現(xiàn)邏輯,使得它們具有兩個(gè)變化的維度甚脉,乃至多個(gè)變化的維度丸升。
如何應(yīng)對(duì)“多種維度變化”?利用OOP技術(shù)使類型可以輕松沿著2個(gè)乃至多個(gè)方向變化牺氨,而不引入額外的復(fù)雜度狡耻?
定義:
將抽象部分(業(yè)務(wù)功能)與實(shí)現(xiàn)部分(平臺(tái)實(shí)現(xiàn))分離,使他們都可以獨(dú)立地變化猴凹。
結(jié)構(gòu):
要點(diǎn)總結(jié):
1.Bridge模式使用“對(duì)象間的組合關(guān)系”解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系夷狰,使得抽象的實(shí)現(xiàn)可以沿著各自的維度來(lái)變化。所謂抽象和實(shí)現(xiàn)研制各自維度的變化郊霎,即“子類化”他們沼头。
2.Bridge模式有時(shí)候類似于多繼承方案,但是多繼承方案往往違背單一職責(zé)原則(即一個(gè)雷只有一個(gè)變化的原因),復(fù)用性較差进倍。Bridge模式是比多繼承更好的解決方案土至。
3.Bridge模式的應(yīng)用一般在“兩個(gè)非常強(qiáng)的變化維度”有時(shí)一個(gè)類也有多余兩個(gè)的變化維度,這是可以使用Bridge的擴(kuò)展模式猾昆。
代碼示例:
實(shí)現(xiàn)一個(gè)Messager陶因,含有基本功能PlaySound,Connect等,并有PC垂蜗、Mobile不同的平臺(tái)實(shí)現(xiàn) 和 精簡(jiǎn)楷扬、完美等不同業(yè)務(wù)功能的版本
方法1
Bridge1.cpp
類的個(gè)數(shù):1 + n + m*n,數(shù)量巨大且不同類之中有大量重復(fù)
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) 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ù)抽象 m
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();
}
方法2
1.觀察上述兩個(gè)類么抗,發(fā)現(xiàn)只有 *messager 聲明不同毅否,故采用基類聲明亚铁,運(yùn)行時(shí)多態(tài)調(diào)用方式蝇刀,創(chuàng)建不同的 PCMessagerBase,Mobilemessager;
class PCMessagerLite {
Messager *messager; // = new PCMessagerBase()或 MobileMessagerBase()
public:
virtual void Login(string username, string password){
messager -> Connect();
//........
}
virtual void SendMessage(string message){
messager -> WriteText();
//........
}
virtual void SendPicture(Image image){
messager -> DrawShape();
//........
}
};
2.考慮步驟2的代碼徘溢,Messager類是純虛基類(抽象類)吞琐,不能實(shí)例化,故= new ...不成立然爆。
分析產(chǎn)生這種狀況的原因站粟,是Login,SendPicture等與平臺(tái)實(shí)現(xiàn)相關(guān)的方法,和PlaySound,DrawShape等與業(yè)務(wù)功能相關(guān)的方法不應(yīng)該在一個(gè)類里曾雕。
將其拆分奴烙,得到MessagerImp類。
同時(shí)將MessagerLite剖张,MessagerPerfect類中相同的MesseagerImp字段提到父類Messager切诀,得到重構(gòu)后的代碼
注意運(yùn)行時(shí)裝配
class Messager{
protected:
MessagerImp* messagerImp;//子類重復(fù)內(nèi)容往上扔,放在父類里搔弄。
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);
}
參考資料:1幅虑、李建忠 C++設(shè)計(jì)模式 2、 博客內(nèi)容 http://www.cnblogs.com/wangxiaobao/