設計模式第一彈,發現在繼續學習Java相關知識前,很有必要學習下設計模式,其中幾個比較重要的設計模式其實在之前的使用中已經出現過很多次,但是由於沒有學過設計模式,也就知其然不知其所以然的拿來用了,所以接下來準備好好整理學習設計模式,博客整理的內容均來自《Head First 設計模式》。下面開始整理第一個設計模式–策略模式
1.概念
策略模式:針對一組算法,將每一個算法分別封裝起來,讓它們之前可以互相替換,此模式讓算法的變化獨立於使用算法的客戶。
2.設計原則
找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。即:封裝變化
把會變化的部分取出並“封裝”起來,以便以後可以輕易地改動或擴充此部分,而不影響到不需要變化的其他部分,總結起來就是:分開變化和不會變化的部分。針對接口編程,而不是針對實現編程
將需要變化的行爲放在分開的類中,此類專門提供某種行爲接口的實現。多用組合,少用繼承
區分“有一個”(組合)關係和“是一個”(繼承)關係,使用 組合建立系統具有很大的彈性,不僅可以將算法族封裝成類,更可以“在運行時動態的改變行爲”。
3.使用舉例
借用《Head First 設計模式》書上的實例:
3.1 假設鴨子類(Duck),有許多不同的子類:
綠頭鴨(MallardDuck)
紅頭鴨(RedHeadDuck)
橡皮鴨(RubberDuck)
誘餌鴨(DecoyDuck)
模型鴨(ModelDuck)
子類繼承父類的行爲:呱呱叫(quack())和游泳(swim())
嗯,一切看起來都很正常,通過繼承,子類得到父類的行爲,這樣子類也會呱呱叫和游泳;
3.2 現在想讓某些鴨子具有新的行爲:飛行(fly())
首先想到的是在需要具有飛行行爲的子類中添加新的方法(fly())不就解決了嗎!嗯,這是在子類比較少的情況下可以使用的“笨辦法”,一旦子類很多,而且也許好幾個子類具有的飛行行爲是一樣的呢?這樣不就產生了大量重複代碼嗎?這是我們需要避免的!
其次想到的是在父類(Duck)中添加行爲(fly()),其子類通過繼承就擁有飛行行爲了。看似問題解決了,但是想想這樣子所有的子類就都擁有了飛行的行爲,包括橡皮鴨(不應該具有飛行的行爲),所以這種方法行不通,因爲通過繼承,一旦改變父類中的某些行爲,所有子類都將受影響,導致某些不應該變化的子類也發生了變化,這種改變將是牽一髮而動全身的;
通過繼承來提供Duck的行爲的缺點:- 導致代碼在多個子類中重複
- 運行時的行爲不容易改變
- 很難知道所有鴨子的全部行爲
接下來想到的方法是通過接口實現,通過將變化的行爲(呱呱叫,游泳,飛行)封裝到單獨的接口中,某個子類想要擁有哪種行爲,實現那種接口,重寫其方法就行了,看似能解決暫時的問題,然而一旦行爲增加(比如每隔一週增加一種新的行爲)或者子類增加,這樣將造成嚴重的混亂局面,所以,這種方法也不合適。
通過把會變的行爲提取出來設計成一個接口的缺點:- 導致重複代碼變多
- 導致代碼無法進行復用
3.3 接下來就是策略模式展現魅力的時候了
通過以上思路,瞭解到添加新的行爲並不是那麼容易,這個過程是變化的,根據設計原則:封裝變化,我們知道變化的部分是鴨子類的行爲,而不變的是鴨子類的繼承關係,把握住了變化與不變的內容,於是進行如下設計:建立對應行爲的接口,比如創建飛行行爲的接口(FlyBehavior),其中有個方法fly(),然後所有的飛行行爲類都實現它,實現其中的fly()方法,結構如下:
其他行爲以此類推,這裏就不重複了。
接下來整合鴨子的行爲,做法是這樣的:
- 首先,在Duck類中定義對應行爲接口的實例,例如上面的飛行行爲:
protected FlyBehavior flyBehavior;
- 然後,實現調用接口行爲的方法,依然使用飛行行爲舉例:
public void performFly() {
flyBehavior.fly();
}
- 接下來是在子類中指定其應具有的行爲
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior =new FlyNoWay();//這裏指定的飛行行爲是“不會飛”
}
}
嗯,以上,基本結構出來了,接下來進行測試,創建測試類:
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performFly();//調用了MallardDuck類的飛行方法,看上面,MallardDuck類指定的飛行行爲是“不會飛”
}
以上,簡單總結了策略模式的使用,使用策略模式的優點如下:
- 採用良好的oo軟件設計原則
- 分開變化和不會變化的部分
- 針對接口編程
4. 實例代碼
實例代碼根據《Head First 設計模式》書上的代碼進行編寫測試,如下:
4.1 首先,封裝變化,即封裝行爲類:
package DuckInterface;
public interface FlyBehavior {
public void fly();
}
package DuckFlyBehavior;
import DuckInterface.FlyBehavior;
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("-->I can fly!!!");
}
}
package DuckFlyBehavior;
import DuckInterface.FlyBehavior;
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("-->I cannot fly...");
}
}
注:另外一個行爲:呱呱叫(quack)結構與飛行行爲結構類似,這裏就不貼出代碼了;
4.2 接下來是定義鴨子父類:
package DuckAbstract;
import DuckInterface.FlyBehavior;
import DuckInterface.QuackBehavior;
public abstract class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public Duck() {
}
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
// 所有鴨子都具有游泳的行爲,所以游泳放在父類中,所有子類都能繼承這個方法
public void swim() {
System.out.println("-->all ducks can swim");
}
public abstract void display();
}
可以看到,在鴨子父類中定義了封裝好的行爲,並調用接口的方法,具體調用哪個方法並不知道,需要根據具體的子類進行調用,下一步就是具體定義不同子類擁有的具體行爲;
4.3 創建子類,繼承父類,定義好不同子類擁有的具體行爲,這裏以綠頭鴨(MallardDuck)示範:
package DuckInstence;
import DuckAbstract.Duck;
import DuckFlyBehavior.FlyNoWay;
import DuckQuackBehavior.MuteQuack;
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new MuteQuack();// 綠頭鴨具有MuteQuack的行爲,就是“不會叫”
flyBehavior = new FlyNoWay();// 綠頭鴨具有FlyNoQWay的行爲,就是“不會飛”
}
@Override
public void display() {
System.out.println("-->this is MallardDuck");
}
}
4.4 最後一步,進行測試:
package DuckInstence;
import DuckAbstract.Duck;
public class TestDuck {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performFly();
mallard.performQuack();
}
}
在主方法中創建MallardDuck類的對象,調用其行爲,結果顯示如下:
以上,通過使用策略模式,很好的解決了不同子類具有不同行爲,但是又互不影響的問題,達到了良好的OO設計必須具備可複用、可擴充、可維護 的特性