設計模式之策略模式
定義:定義了算法族,分別封裝起來,讓它們之間能夠相互替換,此模式讓算法的變法獨立於使用算法的客戶。
關於模式的學習原本就是比較頭痛的事情,若是但看看理論。就算你知道了它應用的場景。在實際的應用中,你也很難的應用到你的項目中。理解容易,實際的應用更難。
接下來我要經過一個實例來說解這個設計模式。經過不斷的需求變化,來真正的體現設計模式帶來的好處,咱們使用一些設計模式,就是讓你的程序能夠應付客戶的不斷的需求變化。咱們知道,在咱們實際的開發中,難的不是技術上的問題,而是客戶隨着開發的進行,他們的需求的不斷的變化,這是最頭痛的,若是你的程序不能對付客戶的變化的話,你就會付出更多的代價。爲了讓咱們的程序能夠健壯,重用,可擴展。利用設計模式的思想去開發編程,能夠解決上面的幾個問題。這並非惟一的辦法,可是比較好的辦法。
咱們的要求是設計一個模擬鴨子游戲的應用程序。(注:這個應用是在Head First 設計模式 一書中的例子)。遊戲中會出現各類鴨子,一邊游泳戲水,一邊呱呱叫。對於這樣的要求,咱們首先會運用OO技術,並利用繼承的思想,設計一個鴨子的超類(Superclass),並讓各類鴨子繼承這個超類。
public class Duck{
public void quack(){ //呱呱叫
System.out.println("呱呱叫");
}
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstract void display(); /*由於外觀不同,讓子類本身去決定了。*/
}
對於它的子類只需簡單的繼承就能夠了,並實現本身的display()方法。
//野鴨
public class MallardDuck extends Duck{
public void display(){
System.out.println("野鴨的顏色...");
}
}
//紅頭鴨
public class RedheadDuck extends Duck{
public void display(){
System.out.println("紅頭鴨的顏色...");
}
}
這裏可能還會有其餘顏色的鴨子。經過這個簡單的繼承咱們知足了客戶的要求。
不幸的是,如今客戶又提出了新的需求,想讓鴨子飛起來。這個對於咱們OO程序員,在簡單不過了,在超類中在加一個方法就能夠了。
public class Duck{
public void quack(){ //呱呱叫
System.out.println("呱呱叫");
}
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstract void display(); /*由於外觀不同,讓子類本身去決定了。*/
public void fly(){
System.out.println("飛吧!鴨子");
}
}
在子類中只需簡單的覆蓋。
//殘廢鴨
public class DisabledDuck extends Duck{
public void display(){
System.out.println("殘廢鴨的顏色...");
}
public void fly(){
//覆蓋,變成什麼事都不作。
}
}
其它會飛的鴨子不用覆蓋。
這樣全部的繼承這個超類的鴨子都會fly了。可是問題又出來了,客戶又提出有的鴨子會飛,有的不能飛。客戶就是那樣的讓咱們頭痛啊,他們的需求會隨着咱們的開發而改變的。
這時咱們須要作的是對會飛的鴨子重寫fly方法,對於不能飛的也重寫,但在方法裏什麼也不寫,只是一個空方法,沒有任何實現。只是簡單覆蓋超類中的fly方法。這樣能夠知足暫時的需求了,但對咱們的維護帶來了麻煩。對於一些不會叫的,也不會飛的鴨子的子類中,夾雜了一些沒有意義的代碼,就是對一些超類方法的覆蓋。
對於上面的設計,你可能發現一些弊端,若是超類有新的特性,子類都必須變更,這是咱們開發最不喜歡看到的,一個類變讓另外一個類也跟着變,這有點不符合OO設計了。這樣很顯然的耦合了一塊兒。
這時,咱們會立刻想到運用接口來消除這種弊端。咱們把容易引發變法的部分提取出來並封裝之,來應付之後的變法。雖然代碼量加大了,但可用性提升了,耦合度也下降了。同時也具備很強的擴展性,也真正的利用了OO設計思想。
咱們把Duck中的fly方法和quack提取出來。
public interface Flyable{
public void fly();
}
public interface Quackable{
public void quack();
}
最後Duck的設計成爲:
public class Duck{
System.out.println(" 游泳");
}
public abstract void display(); /*由於外觀不同,讓子類自 己去決定了。*/
}
而MallardDuck,RedheadDuck,DisabledDuck 就能夠寫成爲:
//野鴨
public class MallardDuck extends Duck implements Flyable,Quackable{
public void display(){
System.out.println("野鴨的顏色...");
}
public void fly(){
//實現該方法
}
public void quack(){
//實現該方法
}
}
//紅頭鴨
public class RedheadDuck extends Duck implements Flyable,Quackable{
public void display(){
System.out.println("紅頭鴨的顏色...");
}
public void fly(){
//實現該方法
}
public void quack(){
//實現該方法
}
}
//殘廢鴨 只實現Quackable(能叫不能飛)
public class DisabledDuck extends Duck implements Quackable{
public void display(){
System.out.println("殘廢鴨的顏色...");
}
public void quack(){
//實現該方法
}
}
這樣已設計,咱們的程序就下降了它們之間的耦合。
如今咱們知道使用繼承並不能很好的解決問題,由於鴨子的行爲在子類中不斷的變化,而且讓所用的子類都有這些行爲是不恰當的。Flyable和Quackable接口一開始彷佛還挺不錯的,解決了問題(只有會飛到鴨子才實現 Flyable),可是Java接口不具備實現代碼,因此實現接口沒法達到代碼的複用。這時咱們有一個設計原則:找出應用中可能須要變化之處,把它們獨立出來,不要和那些不須要變化的代碼混在一塊兒。
如今咱們根據這個設計原則,咱們上面的設計還存在問題。咱們因該如何解決呢?
如今,爲了要分開「變化和不變化的部分」,咱們準備創建兩組類(徹底遠離Duck類),一個是"fly"相關的,另外一個是「quack」相關的,每一組類將實現各自的動做。比方說,咱們可能有一個類實現「呱呱叫」,另外一個類實現「吱吱叫」,還有一個類實現「安靜」。
咱們如何設計實現飛行和呱呱叫的行爲的類呢?咱們爲了讓咱們的程序有彈性,減少耦合。咱們又必須遵循第二個設計原則:針對接口編程,而不是針對實現編程。
看看咱們具體的實現吧,實現勝於理論。
首先寫兩個接口。FlyBehavior(飛行行爲)和QuackBehavior(叫的行爲).
public interface FlyBehavior{
public void fly();
}
public interface QuackBehavior{
public void quack();
}
咱們在定義一些針對FlyBehavior的具體實現。
public class FlyWithWings implements FlyBehavior{
public void fly(){
//實現了全部有翅膀的鴨子飛行行爲。
}
}
public class FlyNoWay implements FlyBehavior{
public void fly(){
//什麼都不作,不會飛
}
}
針對QuackBehavior的幾種具體實現。
public class Quack implements QuackBehavior{
public void quack(){
//實現呱呱叫的鴨子
}
}
public class Squeak implements QuackBehavior{
public void quack(){
//實現吱吱叫的鴨子
}
}
public class MuteQuack implements QuackBehavior{
public void quack(){
//什麼都不作,不會叫
}
}
這樣的設計,可讓飛行和呱呱叫的動做被其餘的對象複用,由於這些行爲已經與鴨子類無關了。而咱們增長一些新的行爲,不會影響到既有的行爲類,也不會影響「使用」到飛行行爲的鴨子類。
最後咱們看看Duck 如何設計。
public class Duck{
FlyBehavior flyBehavior;//接口
QuackBehavior quackBehavior;//接口
public Duck(){}
public abstract void display();
public void swim(){
//實現游泳的行爲
}
public void performFly(){
flyBehavior.fly();//這時鴨子對象不親自處理飛行行爲,而是委託給flyBehavior引用的對象。
}
public void performQuack(){
quackBehavior.quack();();//這時鴨子對象不親自處理叫的行爲,而是委託給quackBehavior引用的對象。
}
}
看看MallardDuck如何實現。
public class MallardDuck extends Duck{
public MallardDuck() {
flyBehavior = new FlyWithWings ();
quackBehavior = new Quack();
//由於MallardDuck 繼承了Duck,全部具備flyBehavior 與quackBehavior 實例變量
}
public void display(){
//實現
}
}
這樣就知足了便可以飛,又能夠叫,同時展示本身的顏色了。
這樣的設計咱們能夠看到是把flyBehavior ,quackBehavior 的實例化寫在子類了。咱們還能夠動態的來決定。
咱們只需在Duck中加上兩個方法。
public class Duck{
FlyBehavior flyBehavior;//接口
QuackBehavior quackBehavior;//接口
public void setFlyBehavior(FlyBehavior flyBehavior){
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior {
this.quackBehavior= quackBehavior;
}
}
你們看到這樣的方法應該知道它是利用依賴注入的思想,動態的來改變鴨子的行爲。
在測試的類中,咱們能夠這樣寫。
public class Test{
public static void main(String[] args){
//通常的用法:
Duck mallard = new MallardDuck();
mallard.performFly();
mallard .perforeQuack();
//動態的改變
mallard.setFlyBehavior(new FlyRocketPowered);//具備火箭動力的飛行能力
mallard.performFly();
}
}
這樣的用法其實就是Spring 中的一個核心機制,Ioc依賴注入思想。
咱們講了這麼些,其實就是策略模式的應用。咱們寫了這麼多,就是爲了讓你更加明白這個設計模式的應用場景。
策略模式:定義了算法族(飛行行爲中的各類實現<當作一族算法>和呱呱叫行爲中的各類實現<當作一族算法>),分別分裝起來,讓它們之間能夠相互調用,此模式讓算法的變法(就是飛行行爲和呱呱叫的行爲)獨立於使用算法的客戶。