這是我第一次寫文章,可能有寫得不好的地方請大佬指正
可能這會是一篇長期連載的設計模式系列哈哈哈
搬至 head First 設計模式 ,這本書真的非常有意思和易懂
首先 要從設計模式入門 ,需要先看一個簡單的模擬鴨子應用開始
JOE的公司做了一套相當成功的模擬鴨子的遊戲,遊戲中會出現各種鴨子 ,一邊游泳戲水,一邊呱呱叫,此係統的內部設計使用了標準的OO設計,設計了一個鴨子的超類(superclass),並讓各種鴨子繼承此超類
所有的鴨子都會呱呱叫和游泳,所以讓超類負責處理這部分的實現程式碼
每個鴨子的子型別負責實現自己的display()行為在螢幕上顯示其外觀
現在主管們決定,需要模擬程式需要會飛的鴨子,在這個時候,Joe告訴主管們,他一個星期就能搞定,這有什麼困難?Joe 需要在duck類中假如fly()方法,然後所有鴨子就會飛了,他很高興的去交差
但是可怕的事情發生了,老闆在看的時候發現很多的橡皮鴨子在螢幕上飛來飛去,於是通知他準備另尋工作了。
原來,Joe忽略了一件事,並非Duck所有的子類都會飛,Joe在Duck類上加上新的行為,會使某些並不適合該行為的子類也具有該行為。現在可好,程式中有一個無生命會飛的東西。
對程式碼所做的區域性修改,影響層面可不只是局面(比如會飛的橡皮鴨)
他體會到了一件事,當涉及“維護”時,為了複用目的而使用繼承的結局並不完美。
public class RubberDuck extends Duck {
@Override
void display() {
System.out.println("我是橡皮鴨");
}
@Override
public void quack() {
System.out.println("修改為吱吱叫");
}
}複製程式碼
當前結構:
Joe突然想到,只要像quack()方法一樣,把fly()方法覆蓋掉就可以了。
@Override
void fly() {
// 什麼事都不做
}複製程式碼
可是又衍生出另一個問題,那我以後要加個木頭鴨呢,不會飛也不會叫。
public class DecoyDuck extends Duck {
@Override
void display() {
System.out.println("我是木頭鴨");
}
@Override
public void quack() {
// 什麼事都不做
}
@Override
void fly() {
// 什麼事都不做
}
}複製程式碼
每次有新的鴨子的子類出現,他就要被迫檢查並可能需要覆蓋掉fly()和quack() ,這簡直是無窮無盡的噩夢。。。
所以,他需要一個更清晰的方法,讓某些鴨子型別可飛或可叫
有一個想法: 把fly()從超類中取出來,這麼一來,只有會飛的鴨子實現flyable介面,同樣的方法可以用在quack(),設計一個quackable介面
public interface Quackable {
void quack();
}複製程式碼
public interface Flyable {
void fly();
}
複製程式碼
實現為繼承 虛線為實現
大家覺得這個設計如何?
可想而知 ,這是一個超笨的方法,這樣一來重複的程式碼會變多,萬一需要修改48個duck的子類的fly行為,你又需要全部都修改了。
我們知道繼承並不是適當的 解決方式,雖然flyable 和quackable可以解決一部分問題,但是造成了程式碼無法複用,這隻能說是從一個噩夢跳到另一個噩夢了。
接著往下看
軟體開發的一個不變真理:不管軟體當初設計得多好,一段時間總是需要成長與改變,否則軟體就會死亡。
我們把問題歸零,現在我們知道繼承並不能很好的解決我們的問題,flyable介面也是。幸運的是有一個設計原則(不是設計模式) 可以幫我們很好的解決這個問題。
設計原則1:找到應用中可能需要變化的地方,把它們獨立出來,不要和那些不需要變化的程式碼混在一起。(這是我們的第一個設計原則,後面還會有)
換句話說,每次新的需求一來,都會使某方面的程式碼發生改變,那麼你就可以確定,這段程式碼需要被抽出來。把需要變化的部分抽取出來並封裝起來,以便以後可以輕易改變或者擴充這部分,不影響不需要變化的部分。
回到我們的問題,把鴨子的行為fly 和quack從duck類中取出。
設計鴨子的行為,我們希望一切能有彈性,我們應該在鴨子類中包含設定行為的方法,這樣就可以在執行時動態的改變綠頭鴨的飛行行為。
有了這些目標,接下來看看第二個設計原則:
設計原則2:針對介面變成,而不是針對實現程式設計
從現在開始,鴨子的行為將被放到分開的類中,此類專門提供某行為介面的實現。這樣,鴨子類就不再需要知道行為的實現細節。
我們利用介面代表每個行為,比方說,flyBehavior 和quackBehavior,而行為的每個實現都將實現其中的一個介面。所以這次鴨子類不會實現flyable和quackable介面,反而由我們製造一組其他類專門實現flyBehavior 和quackBehavior,這個就被稱為行為類。由行為類而不是duck類來實現行為介面。
public interface FlyBehavior {
void fly();
}複製程式碼
public class FlyCanWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("會飛");
}
}複製程式碼
public class FlyNotWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("不會飛");
}
}複製程式碼
public interface QuackBehavior {
void quack();
}複製程式碼
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("呱呱叫");
}
}複製程式碼
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("吱吱叫");
}
}複製程式碼
這樣的設計,可以讓飛行和呱呱叫的動作被其他的物件複用,因為這些行為已經與鴨子類無關了,而我們可以新增一些行為,不會影響到既有的行為,也不會影響使用到飛行的鴨子類
現在開始整合鴨子的行為
做法是這樣:
首先在duck類中加入兩個例項變數,為別為flyBehavior和quackBehavior,每個物件都會動態的設定這些變數以執行時引用正確的行為型別。
我們找兩個類似的方法performFly()和performQuack()取代duck中的fly()和quack()。往下看
@Data
public abstract class Duck {
/* 鴨子游泳 */
public void swim(){}
/* 鴨子形狀 可能有很多種類的鴨子,所以display方法是抽象的 */
abstract void display();
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
void performFly(){
// 每隻鴨子都會引用實現FlyBehavior介面的物件
flyBehavior.fly();
}
void performQuack(){
quackBehavior.quack();
}
}複製程式碼
現在我們來關心如何設定flyBehavior和quackBehavior變數
public class MallardDuck extends Duck {
@Override
void display() {
System.out.println("我是綠頭鴨");
}
/**
* 因為繼承了duck 所以擁有這兩個變數
*/
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyCanWay();
}
}複製程式碼
測試一下
public static void main(String[] args) {
Duck mallardDuck = new MallardDuck();
mallardDuck.performFly();
mallardDuck.performQuack();
}複製程式碼
結果
如何讓鴨子具有動態行為呢?
利用flyBehavior的setter方法,我們可以隨時呼叫setter改變鴨子的行為。
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}複製程式碼
加入我們的新鴨子種類
public class ModelDuck extends Duck {
@Override
void display() {
System.out.println("我是一隻橡皮鴨");
}
public ModelDuck() {
flyBehavior = new FlyNotWay();
quackBehavior = new Squeak();
}
}複製程式碼
此時,我們讓飛擁有火箭飛,和普通飛兩種,實現flyByhavior介面
public class FlyRocket implements FlyBehavior {
@Override
public void fly() {
System.out.println("火箭飛");
}
}複製程式碼
只使得本來不會飛的模型鴨 變成 火箭飛
public static void main(String[] args) {
Duck mallardDuck = new MallardDuck();
mallardDuck.performFly();
mallardDuck.performQuack();
Duck modelDuck = new ModelDuck();
modelDuck.performFly();
modelDuck.setFlyBehavior(new FlyRocket());
modelDuck.performFly();
}複製程式碼
結果:
會飛
呱呱叫
不會飛
火箭飛
好,我們已經深入研究了鴨子模擬器的設計了。下面是模型圖
當你將繼承和實現一起組合使用時就衍生另一個設計原則:
設計原則3:多用組合(繼承加實現),少用繼承
這文到這裡就結束了,其實上面的就是第一個設計模式:也就是策略模式!
策略模式:分別封裝起來,讓他們之間可以互相替換。
總結
其實我這本書還沒看完,看了第一次懵懵懂懂,第二次看就會有很大的理解,第三次看慢慢的輪廓就出現在我的腦海中。我寫這篇文章的時候應該是我第四次邊看邊寫下來。
接下來會研究繼續寫,即使沒人看哈哈哈