本文參照《Head First 設(shè)計(jì)模式》竿奏,轉(zhuǎn)載請(qǐng)注明出處
對(duì)于整個(gè)系列,我們按照這本書的設(shè)計(jì)邏輯眶根,使用情景分析的方式來描述蜀铲,并且穿插使用一些問題,總結(jié)的方式來講述属百。并且所有的開發(fā)源碼记劝,都會(huì)托管到github上。
項(xiàng)目地址:https://github.com/jixiang52002/HFDesignPattern
1.引文
Joe的公司是做模擬鴨子活動(dòng)的游戲而出名族扰,這款游戲取名為SimUDuck厌丑,這款游戲具有非常多的鴨子定欧,一邊游泳一邊呱呱叫。這里的設(shè)計(jì)采用了標(biāo)準(zhǔn)的OO(Object Oriented怒竿,面向?qū)ο螅┑姆绞骄帉懣仇@里有一個(gè)鴨子的超類(SuperClass),后續(xù)所有的鴨子都必須繼承這個(gè)超類耕驰。
1.1OO面向?qū)ο竽P?/h2>
1.2繼承
這時(shí)爷辱,公司的高層們想要通過模擬會(huì)飛的鴨子來追求行業(yè)的領(lǐng)先。然后Joe的項(xiàng)目經(jīng)理拍著胸脯告訴主管朦肘,Joe很快就可以搞定饭弓,“有了OO什么都不怕”
Joe接收到任務(wù)后,想出了一個(gè)辦法:“我僅需要在Duck這個(gè)超類中加上Fly()的方法厚骗,然后所有的鴨子都可以飛了”示启。然后他的設(shè)計(jì)模型就改成以下的樣子
這樣看起來貌似沒有什么問題的,然后可怕的問題發(fā)生了领舰。夫嗓。。冲秽。
用戶反饋舍咖,自己的橡皮鴨和木鴨居然也可以飛起來!o鄙!排霉!
那么,到底是為何導(dǎo)致了這個(gè)可怕的問題民轴?
我們來分析一下:由于Joe在Duck超類中加上fly方法攻柠,導(dǎo)致所有的子類都會(huì)繼承該方法,這就導(dǎo)致了原本不會(huì)飛的橡皮鴨和木鴨也具有飛行能力后裸,顯然為了提高復(fù)用性使用的繼承方式瑰钮,并未達(dá)到完美得結(jié)果。
1.3 繼承+覆蓋
Joe在思考后微驶,又得出一個(gè)方案浪谴,那就是在橡皮鴨中將fly方法覆蓋掉,不做任何操作因苹,這樣原有的RubberDuck類中架構(gòu)就變成如下:
然后苟耻,業(yè)務(wù)的需求又需要加入木鴨(DecoyDuck),它不會(huì)叫也不會(huì)飛扶檐⌒渍龋苦逼的Joe又要把木鴨(DecoyDuck)中quark方法覆蓋,這樣DecoyDuck中的類結(jié)構(gòu)就變成如下:
但是我們會(huì)發(fā)覺款筑,如果以后有新的業(yè)務(wù)官卡,甚至于fly方法中出現(xiàn)一個(gè)bug蝗茁,或者需要?jiǎng)h除fly相關(guān)的業(yè)務(wù),所有相關(guān)代碼都需要修改寻咒,在大型項(xiàng)目中哮翘,這都導(dǎo)致非常可怕的維護(hù)問題毛秘。那么饭寺,到底該怎么辦
1.4 接口
“這不就相當(dāng)于讓我根據(jù)用戶手機(jī)殼顏色換主題嗎?”叫挟,可憐的Joe在快被想要沖上去打產(chǎn)品經(jīng)理的時(shí)候(皮一下艰匙。。抹恳。)员凝。腦子突然想起一件神器,他決定試試奋献。他的方法就是使用接口(Interface)健霹,將fly分離出來,放進(jìn)一個(gè)Flyable接口中瓶蚂。針對(duì)于quark方法糖埋,也可以這樣分離進(jìn)Quarkable接口中,
雖然這樣看起來滿足了我們之前所提到的所有需求窃这,但是這樣一來重復(fù)代碼量會(huì)非惩穑可怕,如果有上萬(wàn)個(gè)Duck子類杭攻,Joe一定會(huì)發(fā)瘋的祟敛。所有,這個(gè)方法雖然看起來很好兆解,但是一旦某個(gè)方法或者行為發(fā)生改變垒棋,我們需要定位到所有實(shí)現(xiàn)該方法的類中去修改對(duì)應(yīng)的代碼,這很容易導(dǎo)致bug的發(fā)生痪宰。
那么,到底有沒有一種能夠建立軟件的有效方法畔裕,能夠讓我們可以對(duì)既有的項(xiàng)目在影響最小的情況下修改他的業(yè)務(wù)邏輯衣撬,這樣我們能夠花很少的時(shí)間去修改代碼。
2.策略模式
第一設(shè)計(jì)原則:
找出應(yīng)用中可能需要變化之處扮饶,把它們獨(dú)立出來具练,不要和那些不需要變化的代碼混合在一起。
按照以上的原則進(jìn)行設(shè)計(jì)甜无,代碼發(fā)生變化引起的后果會(huì)非常小扛点,整個(gè)項(xiàng)目會(huì)特別具有彈性哥遮。
這個(gè)原則不僅僅適用于策略模式,對(duì)于之后講解的模式同樣也是核心的指導(dǎo)方向陵究。那么我們繼續(xù)Joe所遇到的Duck問題眠饮。
2.1分開變化的和不會(huì)變化的
我們很清楚,Duck類內(nèi)部的fly()和quark()伴隨著鴨子的不同會(huì)發(fā)生改變铜邮,其他模塊是不變的仪召。為了要把這兩個(gè)變化的行為從Duck類中分開,我們把它們從Duck類中抽離出來松蒜,建議一組新類用來代表每一個(gè)行為扔茅。
那么,問題來了秸苗。如何設(shè)計(jì)那組實(shí)現(xiàn)飛行行為和呱呱叫行為的一組類呢召娜?
這里就需要提及第二設(shè)計(jì)原則
第二設(shè)計(jì)原則:
針對(duì)于接口編程,不針對(duì)實(shí)現(xiàn)編程
這里我們希望的是在創(chuàng)建具體的Duck類的時(shí)候惊楼,可以動(dòng)態(tài)的生成對(duì)應(yīng)的行為玖瘸。打個(gè)比方,我們想要產(chǎn)生一個(gè)新的綠頭鴨胁后,將制定類型的飛行行為賦予給它店读。這就說明,在Duck類中攀芯,我們需要包含定義Duck行為的方法屯断,這樣在運(yùn)行的時(shí)候,我們就可以動(dòng)態(tài)去改變綠頭鴨的行為侣诺。
所以這里我們使用兩個(gè)接口來代表兩個(gè)行為殖演,這里定義為 FlyBehavior和QuarkBehavior,行為的每次實(shí)現(xiàn)年鸳,都將實(shí)現(xiàn)對(duì)應(yīng)的接口趴久。
但是接口類是沒有方法體的,也就是說搔确,我們需要一組類實(shí)現(xiàn)對(duì)應(yīng)的行為彼棍,這些專用來實(shí)現(xiàn)類似FlyBehavior和QuarkBehavior的一組類,我們稱為行為類膳算。
這里提到的接口類并非嚴(yán)格意義上Java中的接口(Interface)座硕,可以理解為抽象類或接口。這里我們可以理解為:
"針對(duì)接口編程"真正的意思是“針對(duì)超類型(supertype)編程”
這里講得有點(diǎn)難以理解涕蜂,我們對(duì)比一下針對(duì)實(shí)現(xiàn)編程和針對(duì)接口編程的區(qū)別:
//針對(duì)實(shí)現(xiàn)編程
Dog dog=new Dog();
dog.bark();
//針對(duì)接口編程
Animal animal=new Dog();
animal.makeSoud();
2.2實(shí)現(xiàn)鴨子的行為(代碼)
從上面的講解华匾,我們可以理解可變部分是fly和quack兩種方法。不可變?yōu)镈uck類机隙。那么這里我們需要使用兩個(gè)接口FlyBehavior和QuackBehavior蜘拉,還有一些列他們對(duì)應(yīng)的行為類萨西,具體的結(jié)構(gòu)邏輯如下圖:
這設(shè)計(jì)有兩個(gè)很明顯的優(yōu)勢(shì):
可以讓飛行和呱呱的叫的動(dòng)作可以被其他對(duì)象復(fù)用,因?yàn)檫@些行為已經(jīng)與鴨子類無關(guān)旭旭。也就是解耦
我們能夠在不影響原有的行為類的情況下新增一些行為谎脯。也就是具備了彈性和可拓展性
拓展幾個(gè)概念:
耦合指的就是兩個(gè)類之間的聯(lián)系的緊密程度
解耦指的是解除類之間的直接關(guān)系,將直接關(guān)系轉(zhuǎn)換成間接關(guān)系
想要了解的可以參考這篇文章:https://blog.csdn.net/qq_24499615/article/details/77821896
接下來分別將FlyBehavior,FlyWithWings,FlyNoWay分別貼下
public interface FlyBehavior {
//飛行
public void fly();
}
public class FlyWithWings implements FlyBehavior{
public void fly() {
System.out.println("I am flying!");
}
}
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly!");
}
}
接下來將QuackBehavior您机,Quack穿肄,MuteQuack,Squeak類的代碼分別貼下:
public interface QuackBehavior {
//呱呱叫
public void quack();
}
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("<< Slience>>");
}
}
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("Squeak");
}
}
到這里將fly和quack的接口類和行為類完成际看。
2.3組合鴨子行為
在前面2.2我們將飛行(fly)和呱呱叫(quack)的動(dòng)作"委托"(delegate)給其他接口類處理咸产,而并非在Duck類(或者子類)中定于fly和quack方法。那么到底該怎么把行為組合進(jìn)Duck中仲闽?
-
1.首先在Duck類中增加兩個(gè)“實(shí)例變量”脑溢,分別為flyBehavior和quackBehavior,聲明為接口類型(注意不是具體類的實(shí)現(xiàn)類型)赖欣,每個(gè)Duck(或其子類)會(huì)動(dòng)態(tài)的設(shè)置這些變量以在運(yùn)行時(shí)引用正確的行為類型(如FlyWithWings屑彻,Squeak等)。Duck類的類結(jié)構(gòu)如下
2.那么顶吮,就開始實(shí)現(xiàn)Duck類
public abstract class Duck {
//為行為接口類型聲明兩個(gè)引用變量社牲,所有的鴨子(或子類)都繼承它們
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public abstract void display();
public void performQuck(){
//委托給行為處理
quackBehavior.quack();
}
public void performFly(){
//委托給行為處理
flyBehavior.fly();
}
public void swim(){
System.out.println("All ducks float");
}
}
然后我們實(shí)現(xiàn)一個(gè)MallardDuck類來實(shí)現(xiàn)組合,
public class MallardDuck extends Duck{
public MallardDuck(){
//使用FlyWithWings作為其FlyBehavior類型
flyBehavior=new FlyWithWings();
//綠頭鴨使用Quck類處理呱呱叫悴了,
//所以當(dāng)performQuack被調(diào)用時(shí)搏恤,叫這個(gè)行為被委托給Quck對(duì)象
quackBehavior=new Quack();
}
/*
* 因?yàn)镸allardDuck繼承自Duck類
* ,所以具備flyBehavior與quackBehavior實(shí)例變量
*/
public void display() {
// TODO Auto-generated method stub
}
}
當(dāng)然構(gòu)造器內(nèi)還是需要實(shí)現(xiàn)具體行為類湃交,這在之后的模式中會(huì)提供相應(yīng)的解決方案熟空,之后我們會(huì)回歸到這個(gè)問題繼續(xù)解決這個(gè)問題。
到這里搞莺,組合鴨子類已經(jīng)實(shí)現(xiàn)息罗。
- 3.測(cè)試效果
這里我們編譯測(cè)試類
public class MiniDuckSimilator {
public static void main(String[] args) {
Duck mallerdDuck=new MallardDuck();
//一下代碼是將具體的行為委托給對(duì)應(yīng)的行為類處理行為
mallerdDuck.performQuck();
mallerdDuck.performFly();
}
}
2.4 動(dòng)態(tài)行為設(shè)定
在之前的實(shí)現(xiàn)中我們是在Duck的具體子類中實(shí)現(xiàn)FlyBehavior和QuackBehavior的行為,但是Duck失去了動(dòng)態(tài)設(shè)定的功能才沧,對(duì)于追求完美的程序員來說是不可饒恕的迈喉。所以急切需要通過一個(gè)方法動(dòng)態(tài)設(shè)定行為,而并非是在鴨子(Duck)的構(gòu)造器中去實(shí)例化温圆。這里推薦一個(gè)方法-----設(shè)定方法(setter method)
-
在Duck類中增加兩個(gè)新方法 setFlyBehavior()和setQuckBehavior().對(duì)于Duck的類結(jié)構(gòu)修改如下
具體修改如下
-
public abstract class Duck {
//為行為接口類型聲明兩個(gè)引用變量挨摸,所有的鴨子(或子類)都繼承它們
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public abstract void display();
public void performQuck(){
//委托給行為處理
quackBehavior.quack();
}
public void performFly(){
//委托給行為處理
flyBehavior.fly();
}
public void setFlyBehavior(FlyBehavior flyBehavior){
this.flyBehavior=flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior){
this.quackBehavior=quackBehavior;
}
public void swim(){
System.out.println("All ducks float");
}
}
- 2.創(chuàng)建一個(gè)新的鴨子模型:模型鴨(ModelDuck)
public class ModelDuck extends Duck{
public ModelDuck(){
flyBehavior=new FlyNoWay();
quackBehavior=new Quack();
}
public void display() {
System.out.println("I'm a model duck");
}
}
- 3.新建立一個(gè)新的FlyBehavior類型 FlyRocketPowered
public class FlyRocketPowered implements FlyBehavior{
public void fly() {
System.out.println("I'm flying with a rocket!");
}
}
- 4.修改測(cè)試類MiniDuckSimulator,加上模型鴨捌木,并令模型鴨具備火箭動(dòng)力
public class MiniDuckSimilator {
public static void main(String[] args) {
Duck mallerdDuck=new MallardDuck();
//一下代碼是將具體的行為委托給對(duì)應(yīng)的行為類處理行為
mallerdDuck.performQuck();
mallerdDuck.performFly();
Duck modelDuck=new ModelDuck();
//第一次會(huì)使用構(gòu)造參數(shù)里的飛行模式
modelDuck.performFly();
modelDuck.setFlyBehavior(new FlyRocketPowered());
//模型鴨具備火箭飛行能力
modelDuck.performFly();
}
}
運(yùn)行結(jié)果:
到這里我們發(fā)現(xiàn)鴨子模型中我們使用到類的組合使用,而這里我們涉及到第三個(gè)設(shè)計(jì)原則:
第三個(gè)設(shè)計(jì)原則:
多用組合嫉戚,少用繼承
正如我們所見刨裆,組合所建立的系統(tǒng)具備極大的彈性澈圈,不僅僅可以將行為封裝為一系列的行為類,更可以動(dòng)態(tài)改變行為帆啃,只需要組合的行為對(duì)象是符合正確的行為接口標(biāo)準(zhǔn)的瞬女。
3.策略模式講解
總結(jié)之前的三個(gè)設(shè)計(jì)原則:
第一設(shè)計(jì)原則
找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來努潘,不要和那些不需要變化的代碼混合在一起诽偷。
第二設(shè)計(jì)原則
針對(duì)于接口編程,不針對(duì)實(shí)現(xiàn)編程
第三設(shè)計(jì)原則
多用組合疯坤,少用繼承
總結(jié)這三條原則結(jié)合起來就是我們學(xué)習(xí)的第一個(gè)模式:
策略模式 定義了算法族报慕,分別封裝起來,讓它們之間可以相互替換压怠,此模式讓算法的變化獨(dú)立于使用算法的客戶眠冈。