一直想把常見的設(shè)計(jì)模式系統(tǒng)地學(xué)習(xí)一遍仇轻,結(jié)果和大多數(shù)人一樣鸟妙,過了幾天就沒能堅(jiān)持下去了焦人。我發(fā)現(xiàn)學(xué)習(xí)這件事情急不得,往往你越急就越容易半途而廢重父。反而是沉住氣花椭,一天學(xué)習(xí)一點(diǎn),然后把它放在腦子里面房午,在碎片時(shí)間里“拿”出來捋一捋矿辽,像發(fā)酵一樣,漸漸地吸收消化為自己的東西郭厌,然后再開始學(xué)習(xí)下一個(gè)知識點(diǎn)袋倔。好了,閑話說了這么多折柠,我們開始吧宾娜!
昨天學(xué)習(xí)了設(shè)計(jì)模式中的策略模式,這個(gè)模式的思想就是:
- 面向接口編程
- 把應(yīng)用中可能需要變化之處獨(dú)立出來扇售,不要和那些不需要變化的代碼混在一起前塔。
我們再來看看維基百科對于c策略模式的定義:
策略模式作為一種軟件設(shè)計(jì)模式,指對象有某個(gè)行為承冰,但是在不同的場景中华弓,該行為有不同的實(shí)現(xiàn)算法偏形。比如每個(gè)人都要“交個(gè)人所得稅”爸舒,但是“在美國交個(gè)人所得稅”和“在中國交個(gè)人所得稅”就有不同的算稅方法泌豆。
策略模式:
定義了一族算法(業(yè)務(wù)規(guī)則)伤为;
封裝了每個(gè)算法解总;
這族的算法可互換代替(interchangeable)
看的似懂非懂的漠秋,我們通過一個(gè)例子來深入的揣摩這個(gè)神奇的設(shè)計(jì)模式肉盹。
情景:一個(gè)模擬游戲中需要用到很多不同類型的鴨子俺陋,請編寫代碼以生成不同的鴨子對象百宇。
你可能想到編寫一個(gè)Duck抽象超類然后讓具體的鴨子類去繼承它欧引,在子類中重寫超類中的方法,實(shí)現(xiàn)“個(gè)性化”恳谎,于是你寫的代碼可能就是下面這個(gè)樣子的:
public class Duck{
public void quack(){
//鴨子的叫聲
}
public void swim(){
//鴨子會游泳
}
public display(){
//鴨子的羽毛的顏色
}
然后再定義一個(gè)子類:
public class MallardDuck extends Duck{
public void display(){
System.out.println("我的羽毛是綠色的");
}
}
乍一看好像感覺并沒有什么不對爸ゴ恕憋肖?確實(shí)上面這種寫法在項(xiàng)目的初期可能確實(shí)能夠達(dá)到用戶突出的需求,但是需求是可能變化的婚苹,你應(yīng)該思考的是:當(dāng)需求改變的時(shí)候你的代碼是否能夠靈活應(yīng)對岸更,這在工程上叫做低耦合。
比如膊升,現(xiàn)在需求改變了怎炊,我要有一種會飛的鴨子,怎么辦廓译?你可能會說這有什么難的评肆,在Duck類里面添加一個(gè)fly方法不就可以了嗎?但是新的問題又來了:如果你在超類設(shè)置了fly方法非区,就意味著每一個(gè)子類都會擁有fly方法瓜挽,這肯定不能忍啊,你可能又會想到補(bǔ)救措施:在那些不會飛的鴨子子類中重寫一遍fly方法征绸。這樣可以是可以久橙,如果400種鴨子有200種不會飛的鴨子子類,可能你整天都在那里做重寫工作了管怠。
那么用接口呢淆衷?讓那些會飛的鴨子實(shí)現(xiàn)接口中的fly方法?這就是典型的從一個(gè)坑跳到了另外一個(gè)坑渤弛,你現(xiàn)在就要把會飛的那200種鴨子都要實(shí)現(xiàn)一遍fly方法祝拯。
好了,上面我們把問題都梳理了一遍她肯。我們重新審視一次我們遇到的問題佳头。在例子中,總共有兩種飛行行為:會飛的和不會飛的辕宏。我們可以不把它寫進(jìn)類里面畜晰,因?yàn)槲覀円呀?jīng)看到砾莱,不同鴨子的飛行行為是不一樣的瑞筐。這就是我們上面提到的把要變化的部分提取出來。其實(shí)這是設(shè)計(jì)模式中很重要的一個(gè)設(shè)計(jì)原則:多用組合腊瑟,少用繼承聚假。我們設(shè)計(jì)一個(gè)飛行接口,讓具體的飛行行為類去實(shí)現(xiàn)它闰非,到底怎么飛由類自己實(shí)現(xiàn)膘格。而我們的Duck類將飛行接口當(dāng)成是鴨子的一個(gè)成員變量。由于Duck類知道這個(gè)飛行行為接口肯定有一個(gè)fly方法财松,所以我可以大膽的在Duck類里面調(diào)用這個(gè)變量的fly方法(這就是面向接口編程)瘪贱,而具體怎么飛要由具體的飛行行為來決定纱控,這在Java中叫做后期綁定。
原理解釋清楚了菜秦,上代碼:
鴨子類:
public abstract class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public abstract void display();
public void performQuack(){
quackBehavior.quack();
}
public void performFly(){
flyBehavior.fly();
}
public void swim(){
System.out.println("所有的鴨子都能漂浮于水面甜害。");
}
public void setFlyBehavior(FlyBehavior flyBehavior){
this.flyBehavior=flyBehavior; //為了代碼更加靈活,這里添加設(shè)置
飛行行為的方法球昨,下同
}
public void setQuackBehavior(QuackBehavior quackBehavior){
this.quackBehavior=quackBehavior; //修改鴨子的叫聲尔店,有的呱呱叫,有的小鴨還不會叫
}
}
飛行行為接口:
public interface FlyBehavior {
void fly();
}
叫聲接口:
public interface QuackBehavior {
void quack();
}
具體的飛行行為類one:
public class FlyWithWings implements FlyBehavior {
public void fly(){
System.out.println("我可以飛主慰。");
}
}
具體的飛行行為類two:
public class FlyNoWay implements FlyBehavior {
public void fly(){
System.out.println("我不能飛");
}
}
具體的叫聲類one:
public class Quack implements QuackBehavior {
public void quack(){
System.out.println("呱呱叫");
}
}
具體的叫聲類two:
public class MuteQuack implements QuackBehavior {
public void quack(){
System.out.println("<我不能叫>");
}
}
好了嚣州,部件代碼都寫好了,我們來講它組裝組裝:
野鴨:
public class MallardDuck extends Duck {
public MallardDuck(){
quackBehavior=new Quack();
flyBehavior=new FlyWithWings();
}
public void display(){
System.out.println("我是野鴨");
}
}
模型鴨(假的鴨子):
public class ModelDuck extends Duck {
public ModelDuck(){
setFlyBehavior(new FlyNoWay());
setQuackBehavior(new MuteQuack());
}
public void display(){
System.out.println("我是模型鴨");
}
}
我們可以感受到共螺,經(jīng)過改寫后的代碼靈活多了该肴,這種靈活主要是來源于可以動態(tài)的組合鴨子的行為,而非死板的繼承方式璃谨。
我們再次復(fù)習(xí)一下策略模式的幾個(gè)設(shè)計(jì)原則作為結(jié)尾吧:
- 面向接口編程沙庐,而非面向?qū)崿F(xiàn)編程
- 多用組合,少用繼承
- 抽離易改變的代碼
我是在校學(xué)生佳吞,目前大三拱雏,如有寫的不對的地方,歡迎指正底扳,謝謝铸抑!另外,設(shè)計(jì)模式文章準(zhǔn)備寫成一個(gè)系列,歡迎follow( _ )