设计模式初学者系列-策略模式 -------为什么总是继承
設(shè)計(jì)模式初學(xué)者系列-策略模式
??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? -------為什么總是繼承
模板方法的延續(xù)
這篇稿子是基于我的前一篇模板方法設(shè)計(jì)模式之上演繹的,如果沒有閱讀請點(diǎn)擊這里查看,以了解這篇稿子的上下文。
在模板方法設(shè)計(jì)模式里我舉了一個(gè)例子:教育部規(guī)定了新生報(bào)到流程的算法骨架,然后這個(gè)算法骨架中的一些關(guān)鍵步驟由各高校自由的去發(fā)揮。我在這個(gè)例子中將高校設(shè)為一個(gè)抽象類,各高校要實(shí)現(xiàn)的算法步驟都是抽象方法。我還給出了兩個(gè)高校的實(shí)現(xiàn)代碼:清華大學(xué)和北京大學(xué)。在這個(gè)例子中本沒有什么問題,但是軟件總是會(huì)變的。
當(dāng)有更多的高校要實(shí)現(xiàn)的時(shí)候,我們就會(huì)發(fā)現(xiàn),很多高校的有些報(bào)到步驟實(shí)現(xiàn)是一樣的,這就存在子類中有大量的重復(fù)代碼,重復(fù)總是會(huì)出問題的。當(dāng)然我們可以使用Martin Fowler的Pull Up Method(Refactoring P320)重構(gòu)方法,將這些共同的部分推移到高校這個(gè)父類實(shí)現(xiàn),并將這個(gè)抽象類改為virtual。
public abstract class 高校
{
public void 報(bào)到()
{
教務(wù)處報(bào)到();
繳費(fèi)();
本院系報(bào)到();
教材科發(fā)教材();
}
protected abstract void 教務(wù)處報(bào)到();
//方法由抽象的更改為虛方法
protected virtual void 繳費(fèi)()
{
//將這個(gè)方法在父類去實(shí)現(xiàn),因?yàn)楹枚喔咝5膶?shí)現(xiàn)都是這樣的,避免重復(fù)
}
protected abstract 專業(yè)等信息 本院系報(bào)到();
protected abstract 教材 教材科發(fā)教材();
}
但是,現(xiàn)在出現(xiàn)了這樣的情況:A,B,C等幾個(gè)大學(xué)的實(shí)現(xiàn)在某些步驟上有些相同,D,F在某些步驟的實(shí)現(xiàn)有些相同,也許你會(huì)說:這不好辦,繼續(xù)使用繼承唄,將共同的東西往上推,并且在“高校類”和各高校實(shí)現(xiàn)的類中間插入一些類,這些類將提供共同的實(shí)現(xiàn)。好像是個(gè)很好的辦法。來瞧一瞧:
重復(fù)的代碼確實(shí)減少了很多,但是還有一些重復(fù)(心里在默默的罵道:TMD,為什么C#不支持多繼承,不然我就可以消除重復(fù)了),也許你還在自我陶醉的欣賞著自己多么完美的類繼承層次,在那里感慨OO的強(qiáng)大。但是隨著具體的高校越來越多,而且有的高校的報(bào)到步驟居然要發(fā)生改變,你小心的在中間那一層添加新的類,并將一些高校的實(shí)現(xiàn)轉(zhuǎn)移,每一次你都非常小心(這個(gè)系統(tǒng)正在高速的運(yùn)轉(zhuǎn),每改錯(cuò)一步,就有多少莘莘學(xué)子入不了學(xué))。你心里終于對OO不滿起來:為什么,為什么大家都說OO是救世主,但是卻救不了我。答案是因?yàn)槟銓O的設(shè)計(jì)原則遺忘在課本里了。開閉原則、優(yōu)先使用組合,你還記得嗎?
在我們很多OO程序員的腦子里總是存在這樣一個(gè)觀念:沒有繼承的程序不是OO的程序,看到重復(fù)總是想到繼承。當(dāng)初我也是這樣想的,有的時(shí)候看到自己畫的龐大的繼承類圖,心里在樂呵呵的笑。可繼承總是不給面子,一個(gè)小小的變化就將這個(gè)看似穩(wěn)定的體系弄的支離破碎。
還是回到我們的例子,在這個(gè)例子中變化的是各高校的報(bào)到步驟,本著發(fā)現(xiàn)變化、封裝變化、隔離變化的原則我們將報(bào)到的步驟分離出來,獨(dú)立成類。
這樣我們就可以復(fù)用這些步驟了,有新的步驟實(shí)現(xiàn)只要添加更多的子類,并不需要修改原來的代碼。(作業(yè):在繼續(xù)閱讀之前根據(jù)上面的類圖自己寫出實(shí)現(xiàn)的代碼來)
這就是所謂的策略設(shè)計(jì)模式:策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨(dú)立于算法的客戶(DP)。
策略模式有三種參與者:
一、 Context 這個(gè)類保存了對策略的引用,并且調(diào)用實(shí)際的策略實(shí)現(xiàn),有可能還提供一個(gè)接口,讓策略可以訪問它內(nèi)部的數(shù)據(jù),在這里就是我們的“高校”類。
二、 Strategy 策略接口,給算法族定義一個(gè)通用的接口,讓客戶以一種一致的方法去訪問。(I教務(wù)處報(bào)到,I繳費(fèi))
三、 ConcreteStrategy 這就是具體的策略實(shí)現(xiàn)了,實(shí)現(xiàn)策略接口(各報(bào)到步驟的實(shí)現(xiàn))。
如下圖:
在我們的例子中報(bào)到的步驟就是算法族 比如“繳費(fèi)”這個(gè)步驟,有多種繳費(fèi)方式,我們將其封裝起來,客戶調(diào)用的時(shí)候并不需要了解你是怎么實(shí)現(xiàn)這個(gè)“繳費(fèi)”的,這個(gè)過程對于客戶來說是透明的。這些不同的“繳費(fèi)”步驟之間是可以無縫的替換,而客戶對此一點(diǎn)都不知覺。
好了,既然解決方案提出來了,我們就來實(shí)現(xiàn)它吧
首先我們定義所有的報(bào)到步驟的接口:
public interface I教務(wù)處報(bào)到
{
void 教務(wù)處報(bào)到();
}
public interface I繳費(fèi)
{
void 繳費(fèi)();
}
public interface I本院系報(bào)到
{
void 本院系報(bào)到();
}
public interface I教材科發(fā)教材
{
void 教材科發(fā)教材();
}
下面我實(shí)現(xiàn)兩個(gè)教務(wù)處報(bào)到的步驟,其他的就當(dāng)作課后作業(yè)了,呵呵。
public class 教務(wù)處報(bào)到A : I教務(wù)處報(bào)到
{
public void 教務(wù)處報(bào)到()
{
Console.Write("教務(wù)處報(bào)到,A類實(shí)現(xiàn)");
}
}
public class 教務(wù)處報(bào)到B : I教務(wù)處報(bào)到
{
public void 教務(wù)處報(bào)到()
{
MessageBox.Show("教務(wù)處報(bào)到,B類實(shí)現(xiàn)");
}
}
再看看我們的高校類的實(shí)現(xiàn)吧:
public class 高校
{
public 高校(I教務(wù)處報(bào)到 p教務(wù)處報(bào)到,I繳費(fèi) p繳費(fèi),I本院系報(bào)到 p本院系報(bào)到,I教材科發(fā)教材 p教材科發(fā)教材)
{
this._教務(wù)處報(bào)到 = p教務(wù)處報(bào)到;
this._繳費(fèi) = p繳費(fèi);
this._本院系報(bào)到 = p本院系報(bào)到;
this._教材科發(fā)教材 = p教材科發(fā)教材;
}
//為什么有了賦值的構(gòu)造函數(shù)還要暴露這么多只寫屬性出來呢?
//這樣就可以在運(yùn)行時(shí)改變高校的報(bào)到步驟了,
//假如報(bào)到系統(tǒng)出現(xiàn)故障我們可以馬上采取另外一種方案
//而不需要停止系統(tǒng)的運(yùn)行
I教務(wù)處報(bào)到 _教務(wù)處報(bào)到;
public I教務(wù)處報(bào)到 教務(wù)處報(bào)到
{
set
{
_教務(wù)處報(bào)到 = value;
}
}
I繳費(fèi) _繳費(fèi);
public I繳費(fèi) 繳費(fèi)
{
set
{
_繳費(fèi) = value;
}
}
I本院系報(bào)到 _本院系報(bào)到;
public I本院系報(bào)到 本院系報(bào)到
{
set
{
_本院系報(bào)到 = value;
}
}
I教材科發(fā)教材 _教材科發(fā)教材;
public I教材科發(fā)教材 教材科發(fā)教材
{
set
{
_教材科發(fā)教材 = value;
}
}
//用上了策略模式,模板方法更加靈活了
//但現(xiàn)在還是不是模板方法了?
public void 報(bào)到()
{
教務(wù)處報(bào)到.教務(wù)處報(bào)到();
繳費(fèi).繳費(fèi)();
本院系報(bào)到.本院系報(bào)到();
教材科發(fā)教材.教材科發(fā)教材();
}
}
Ok,我就把代碼寫這么多了,要這個(gè)代碼運(yùn)行起來還需要一些補(bǔ)充,這個(gè)高校類如何進(jìn)行實(shí)例化才能更靈活也值得考慮。
看到?jīng)],利用組合我們也可以達(dá)到代碼復(fù)用的目的,而且沒有繼承的弊端。
上面好像都是在說策略模式的好話,那策略模式有沒有副作用呢?當(dāng)然有
一、 雖說客戶代碼無須關(guān)心各個(gè)策略是如何實(shí)現(xiàn)的,但是它們還是要知道有多少種策略實(shí)現(xiàn),該實(shí)現(xiàn)是干什么的,也就是客戶代碼需要知道策略的一些細(xì)節(jié),這樣才可以根據(jù)需要使用哪個(gè)策略,但是我們可以使用創(chuàng)建型模式來解決這個(gè)問題。
二、 有的時(shí)候策略需要從Context那里獲取一些數(shù)據(jù),這樣造成雙向的關(guān)聯(lián),而且有可能幾個(gè)策略需要的數(shù)據(jù)都不一樣,但是為了一致性不得不向它們傳遞相同的數(shù)據(jù)。
三、 也許大家會(huì)發(fā)現(xiàn),使用策略模式后出現(xiàn)很多小類,實(shí)際上這也是所有設(shè)計(jì)模式的“通病”。
現(xiàn)實(shí)中的策略模式
大家對于PetShop這個(gè)應(yīng)用肯定很熟悉,在PetShop 4.0里面就使用了策略設(shè)計(jì)模式:
在Petshop4的BLL項(xiàng)目中有一個(gè)OrderAsynchronous類和一個(gè)OrderSynchronous類,它們都繼承自IorderStrategy。OrderSynchronous以一種同步的方式處理訂單,而OrderAsynchronous先將訂單放在隊(duì)列里,然后再對隊(duì)列里的訂單進(jìn)行處理,以一種異步方式。而在BLL中的Order類里通過反射從配置文件讀取策略配置的信息以決定到底是使用哪種訂單處理方式。這里就不貼代碼了,有興趣的可以去下載PetShop看看,主要關(guān)注這幾個(gè):PetShop.BLL.Order(如何使用策略以及如何根據(jù)配置文件實(shí)例化具體的策略)、PetShop.IBLLStrategy. IorderStrategy(策略的接口)、PetShop.BLL. OrderSynchronous、PetShop.BLL. OrderAsynchronous。
總結(jié)
在本篇我們從模板方法談起,聊了一些模板方法隨著項(xiàng)目的發(fā)展可能造成的問題,但這并不是模板方法的弊端,模板方法關(guān)注的是算法骨架的復(fù)用,如果你發(fā)覺新的問題出現(xiàn),這可能就是模板方法不再適用的信號(hào)。通過我們對項(xiàng)目的擴(kuò)展,發(fā)現(xiàn)繼承在某些時(shí)候并不是都能達(dá)到代碼復(fù)用的目的,這個(gè)時(shí)候我們應(yīng)該考慮組合了,而且繼承是一種靜態(tài)的編譯期的行為(針對像C#這種強(qiáng)類型靜態(tài)語言而言),代碼一經(jīng)寫定我們就沒有選擇的余地了。
前幾天和別人在群里閑聊,談到怎樣學(xué)習(xí)設(shè)計(jì)模式,有人說設(shè)計(jì)模式靠悟,有人說設(shè)計(jì)模式靠經(jīng)驗(yàn)的積累。悟也好,經(jīng)驗(yàn)積累也好,我的感覺是不要把設(shè)計(jì)模式當(dāng)作圣經(jīng),當(dāng)一個(gè)人把一個(gè)事物當(dāng)作圣經(jīng)的時(shí)候總是很珍惜她,而且不會(huì)去褻瀆她,這是學(xué)習(xí)模式的障礙。對于初學(xué)者來說應(yīng)該有“熟讀唐詩三百首,不會(huì)吟詩也會(huì)吟”的決心。
轉(zhuǎn)載于:https://www.cnblogs.com/yuyijq/archive/2008/01/14/1038286.html
總結(jié)
以上是生活随笔為你收集整理的设计模式初学者系列-策略模式 -------为什么总是继承的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows图片查看器不出现下一张和上
- 下一篇: 设计模式学习笔记九:原型模式(Proto