繼承、組合和接口用法——策略模式複習總結

前言——爲何繼承不被優先推薦使用

先看這樣一個案例——有一羣鴨子,有的鴨子會游泳,有的鴨子會呱呱叫,每一種鴨子的外貌都不一樣。html

初版——使用繼承

RD 設計了一個鴨子類,做爲全部鴨子的超類。鴨子會呱呱叫(Quack)、也會游泳(Swim),那麼由超類負責處理這部分的實現, 還有一個負責展現鴨子的外貌的 display 方法,它是抽象的,由各個具體的鴨子描述本身的外貌。代碼以下:java

public abstract class Duck {
    public void quack() {
        System.out.println("呱呱叫");
    }

    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();
}
 
/////////////////////////////////
public class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外觀是綠頭");
    }
}
 
/////////////////////////////////
public class RedheadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外觀是紅頭");
    }
}

增長需求

產品:通過市場調研,發現市面上不少相似的系統,都實現了鴨子飛翔的功能,爲了保持競爭力,但願咱們的鴨子也能飛web

RD :只要在 Duck 抽象類中加上 fly() 方法,而後讓全部鴨子都繼承fly(),就ok了算法

問題:並非全部的鴨子都必須會飛,這也符合產品(動物)的多樣性原理。故實際的實現中,全部的鴨子都繼承了這個超類,從而都有了(有的是被動的)飛翔的能力,這顯然是很差的設計,甚至後患無窮。編程

當涉及「維護」時,爲了「複用」目的而使用繼承,並不完美

實際開發中常常見有人這樣作——子類若是不須要父類的某個方法,就強行覆蓋。設計模式

問題:利用繼承來提供 Duck 的行爲,會致使下列的一些問題:服務器

一、代碼在多個子類中無心義的重複——好比玩具鴨子不須要飛翔,可是還得必須顯示的覆蓋這部分的代碼。app

二、運行時的行爲不容易改變——代碼都繼承到了子類,等於代碼是被寫死了。ide

三、沒法靈活的擴展——好比,新加入了木頭的玩具鴨子,木頭的鴨子不會呱呱叫,也不會飛翔,這就仍然須要很笨重的給木頭鴨子覆蓋呱呱叫+飛翔的方法,讓其什麼都不作。工具

四、很難知道全部鴨子所有真正的行爲,沒法容易的獲得,某個鴨子類,到底須要實現的行爲是什麼。

五、牽一髮動全身,形成其餘鴨子不想要的改變——好比又要給鴨子增長跳舞的行爲,那麼全部的不須要跳舞的鴨子,也都要去修改…… 

第二版——使用接口+組合

初版方案不是很完美,因此須要一個更清晰的策略,只讓部分鴨子類型可飛或可叫。能夠把 fly() 從 Duck 超類中抽象出來,用一個 FlyAble 接口來實現,讓只有會飛的鴨子實現此接口,一樣的方式,也能夠設計一個 QuackAble 接口。

public interface Quackable {
    void quack();
}
 
////////////////////
public interface Flyable {
    void fly();
}
 
/////////////////////
public abstract class Duck {
    public void swim() {
        System.out.println("游泳");
    }

    public abstract void display();
}
 
////////////////////
public class MallardDuck extends Duck implements Flyable, Quackable {
    @Override
    public void display() {
        System.out.println("外觀是綠頭");
    }

    @Override
    public void fly() {
        System.out.println("飛翔");
    }

    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}

爲了使用接口而使用接口——效果會拔苗助長

在本案例中,雖然使用 Flyable 與 Quackable 接口能夠解決初版繼承帶來的問題,可是卻產生了代碼沒法複用的新問題。由於 Java 的接口在 1.8 以前,不能具備實現代碼,因此除非你能確定全部使用這個系統的人都是使用的 JDK 8及其之後的版本,不然繼承接口沒法 100% 確保達到代碼的複用目的。

這就意味着:不管什麼時候你須要修改某個行爲,你必須得往下追蹤,並在每個定義此行爲的類中修改它,一不當心,可能會形成新的錯誤。幸運的是,有一些設計原則,剛好適用於此情況。

第一設計原則:找出應用中變化之處,把它們獨立,不要和那些不須要變化的代碼混在一塊兒

把會變化的代碼提取,並封裝爲類,以便將來能夠輕易地改動或擴充此部分,而不會影響其餘不須要變化的部分。能夠說這個原則是每一個設計模式的精髓。

分析:最開始的 Duck 抽象類裏的 fly 方法和咕咕叫(quack)方法都會隨着鴨子的種類不一樣,而被改變。那麼能夠把這兩個方法提取,創建一組新的類來實現。創建兩個類,一個是「fly」相關的,一個是「quack」相關的,每個類能實現各自的多樣化的動做。好比:

一、叫的動做,能夠是 「呱呱叫」,「吱吱叫」,或者 「安靜(不叫)」等

二、飛的動做,能夠是「快速的飛」,「不能飛」等,能知足,相似橡皮鴨不會飛這種新需求

把動做相關的代碼,提取爲類,看着好像不是特別好,畢竟你們都知道:接口才表明的是行爲(或者說動做),類表明的是某種事物的具體類型或者抽象的某種類型。參考:什麼時候使用接口(抽象類)?沒錯,確實須要接口來表示動做,所以引出第二個設計原則。

第二設計原則:面向接口編程而不是面向具體實現

固然不能直接用類來實現真正的動做,因此利用接口表明每一個行爲,設計兩個表明鴨子動做的接口:FlyBehavior 與 QuackBehavior,而第一原則裏說的抽取出的兩種新類(一個是「fly」相關的,一個是「quack」相關的),做爲具體的行爲的實現類,即讓 「fly」 相關的,和 「quack」 相關的表明鴨子的全部具體動做的類都實現其中的一個接口。

public interface FlyBehavior {
    void fly();
}
 
////////////////////
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("飛翔");
    }
}
 
//////////////////
public class FlyWithoutWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不能飛");
    }
}

一、鴨子類 Duck 再也不負責實現 Flying 與 Quacking 接口,反而是額外製造一組其餘類專門實現 FlyBehavior 與 QuackBehavior,這就是所謂的分離變和不變的部分,把變的部分抽取(抽象)——由專門的行爲類而不是 Duck 類來實現行爲接口。從次之後,Duck類就不須要維護常常須要變更的行爲了。

public abstract class Duck {
    public void swim() {
        System.out.println("全部的鴨子都會游泳,不會改變");
    }
 
    abstract void display(); // 全部鴨子都有外貌
}

二、使用接口表明抽象的行爲,具體的鴨子類只須要按照自身的需求,去實現對應的行爲接口( FlyBehavior 、 QuackBehavior等,之後能夠擴展),等 Duck 須要使用某個行爲的時候,具體的實現不會綁死在具體的鴨子類上。

public abstract class Duck {
    FlyBehavior flyBehavior; // 面向接口編程,這也是組合的體現
    QuackBehavior quackBehavior; // 面向接口編程// 面向接口編程
    public void setFlyBehavior (FlyBehavior fb) {
        flyBehavior = fb;
    }
 
    // 面向接口編程
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
 
    abstract void display(); // 全部鴨子都有外貌
 
    public void performFly() {
        flyBehavior.fly();
    }
 
    public void performQuack() {
        quackBehavior.quack();
    }
 
    public void swim() {
        System.out.println("全部的鴨子都會游泳,不會改變");
    }
}

之後,在新設計中,鴨子的具體的類將使用接口(FlyBehavior與QuackBehavior)表示行爲,因此實際的「實現」不會被綁死在鴨子的子類中。

並且,這裏之因此使用抽象類表明Duck,仍是考慮到,既然Duck沒有須要變化的部分了,那麼徹底可讓其表明一個類型——鴨子,故沒有必要使用接口。

問題:爲何非要把行爲設計成接口,而不用抽象類

這個問題說明沒有真正理解接口,要理解 「面向接口編程」 的真正意思——針對超類型編程。這裏所謂的「接口」有多個含義,接口是一個廣義的 「概念」,只不過在 Java 裏特指的 interface。

面向接口編程,關鍵就在實現多態,和能利用多態,程序能夠針對超類型編程,執行時會根據實際情況執行到真正的行爲,不會被綁死在超類型的行爲上,只不過這裏優先使用的接口。

public interface FlyBehavior {
    void fly();
}
 
/////////////////////////
public interface QuackBehavior {
    void quack();
}
 
///////////////////////
public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying!!");
    }
}
 
public class FlyRocketPowered implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying with a rocket");
    }
}
 
public class FlyNoWay implements FlyBehavior {
    public void fly() {
        System.out.println("I can't fly");
    }
}
 
//////////////////////
// 呱呱的叫
public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("Quack");
    }
}
 
// 嘶啞的叫
public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("MuteQuack");
    }
}

///////////////////
public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;public void setFlyBehavior (FlyBehavior fb) {
        flyBehavior = fb;
    }
 
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
 
    abstract void display();
 
    public void performFly() {
        flyBehavior.fly();
    }
 
    public void performQuack() {
        quackBehavior.quack();
    }
 
    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }
}
 
// 能呱呱的叫,能飛
public class MallardDuck extends Duck {
    public MallardDuck() {
        setQuackBehavior(new Quack());
        setFlyBehavior(new FlyWithWings());
    }
 
    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}
 
// 不能飛,能嘶啞的叫
public class DecoyDuck extends Duck {
    public DecoyDuck() {
        setFlyBehavior(new FlyNoWay());
        setQuackBehavior(new MuteQuack());
    }
    public void display() {
        System.out.println("I'm a duck Decoy");
    }
}
 
////////////////// 客戶端
public class MiniDuckSimulator1 {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck(); 
        mallard.performQuack(); // 咕咕叫
        mallard.performFly(); // 能飛
   
        Duck decoyDuck = new DecoyDuck();
        decoyDuck.performFly(); // 不能飛
        // 客戶端能夠動態的改變鴨子的行爲,讓它能飛了
        decoyDuck.setFlyBehavior(new FlyRocketPowered());
        decoyDuck.performFly(); // 嘶啞的叫

    }
}

如上,也是策略模式的體現——既能夠實現代碼複用,又能實現責任分離,即便新增了行爲,也不會影響現有的鴨子類。

總結:何時開始優化

雖然,過早優化是萬惡之源,可是相似接口,抽象類,繼承等這樣基本的思想仍是要從最開始就伴隨整個系統的,好比策略模式這種簡單的設計模式,徹底能夠在開始設計的時候,就考慮進去,直接實現,而不是後期重構。

類可否表明行爲?

前面的例子裏,優化的方案,使用了類表明行爲,雖然具體的行爲實現,仍是應用的接口,那麼這裏合理麼?

合理,衆所周知,在OOP中,類表明某個事物,某個具備狀態的事物的類型,而行爲有時候也是會有狀態的,好比飛翔,能夠有飛翔速度等屬性,所以,在本例,飛行這個行爲也是一種類型,只不過是湊巧的。爲了方便,使用的類表明具體行爲,而行爲的動做實現,仍然是接口表明

算法族的概念

到這裏,再也不把鴨子的各個行爲說成「一組行爲」、或者「相關的行爲」了,而是開始把行爲當作是「一族算法」。算法表明鴨子能作的事情(不一樣的叫法和飛法)

總結:遵循組合(聚合)優於繼承的設計原則

記住,考慮到在一樣可行的狀況下,優先使用組合而不是繼承,也有書中,管他叫:has-a 關係,好於 is-a 關係,前者是組合,後者是繼承。

結合前面的例子,知道繼承關係的耦合度很高,一處改可能會致使到處須要修改。若是一個業務功能,繼承能夠實現,組合也能實現,優先使用組合。由於:

一、組合的關係具備很大的彈性

二、組合更好的分離了責任

三、組合的反作用很小

……

不少的設計模式,都巧妙的體現了該原則。

《Thinking in java》:「繼承要慎用,其使用場合僅限於你確信使用該技術有效的狀況。一個判斷方法是,問一問本身是否須要重新類向基類進行向上轉型。若是是必須的,則繼承是必要的。反之則應該好好考慮是否須要繼承……當用繼承的時候,確定是須要利用多態的特性。若是用不到多態的特性,繼承的關係是無用的」

《effective Java》:「只有當子類真正是超類的子類型時,才適合用繼承。換句話說,對於兩個類A和B,只有當二者之間確實存在is-a關係的時候,類B才應該繼續類A。」

JDK 8 中的接口新特性,能夠改善一些舊問題

https://www.zhihu.com/question/41166418

Java 8的接口,即使有了default method,還暫時沒法徹底替代抽象類。它不能擁有狀態,只能提供公有虛方法的默認實現。Java 9的接口已經能夠有非公有的靜態方法了。將來的Java版本的接口可能會有更強的功能,或許能更大程度地替代本來須要使用抽象類的場景。


做者:RednaxelaFX
連接:https://www.zhihu.com/question/41166418/answer/139494009
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

策略模式究竟是怎麼運行的

StrategyPattern:策略模式也算比較簡單的,同工廠模式同樣都屬於面向接口編程……策略模式是對象的行爲模式之一,而工廠模式是對象的建立模式

策略模式對一系列的算法加以封裝,爲全部算法定義一個抽象的接口,並經過繼承(實現)該接口,對全部算法加以封裝和實現,具體的算法選擇由客戶端決定(策略)。

策略模式使得算法能夠在不影響到客戶端的狀況下發生變化。Strategy 模式主要用來平滑地處理算法的切換 。

簡單說:策略模式可讓咱們在程序中隨意的、快速的替換接口的實現「算法」,並且還不用修改接口,也不須要修改客戶端。能夠說策略模式,是接口的典型應用。

前面說了策略模式封裝算法,天然有一個算法接口 IStrategy,擴展的不一樣的策略(算法封裝)StrategyABuilder,StrategyBBuilder……再來一個策略的容器——其實就是一個工廠,下面總結下策略模式的角色:

Strategy接口 : 策略(算法)的接口

ConcreteStrategy :各類策略(算法)的具體實現

Context:策略的外部封裝類,或者說策略的容器類。它根據不一樣策略執行不一樣的行爲。策略由外部環境決定,天然這個工廠就須要聚合策略接口的引用,並配有對應的執行策略的方法。

好比有這樣一個程序,給文件加密的程序,當前有三種加密方法能夠選用,分別是MD五、RSA,和AES加密算法,下面運用策略模式,看代碼實現;

public interface EncryptStrategy {
    /**
     * 加密算法的抽象接口
     */
    void doEncrypt();
}

public class AESEncryptBuilder implements EncryptStrategy {
    @Override
    public void doEncrypt() {
        System.out.println("進行AES加密!");
    }
}

public class MD5EncryptBuilder implements EncryptStrategy {
    @Override
    public void doEncrypt() {
        System.out.println("進行MD5加密!");
    }
}

public class RSAEncryptBuilder implements EncryptStrategy {
    @Override
    public void doEncrypt() {
        System.out.println("進行RSA加密!");
    }
}

public class Factory {
    /**
     * 聚合的算法接口
     */
    private EncryptStrategy encryptStrategy;

    public Factory(EncryptStrategy encryptStrategy) {
        this.encryptStrategy = encryptStrategy;
    }

    /**
     * 執行加密操做
     * 自己不實現加密算法,而是轉而去調用聚合的算法接口持有的方法
     */
    public void execute() {
        this.encryptStrategy.doEncrypt();
    }
}

public class TestStrategy {
    public static void main(String[] args) {
        Factory factory = new Factory(new MD5EncryptBuilder());
        factory.execute();
    }
}

是否是很是簡單,和工廠模式有有些相似。想到這裏,聯繫以前的工廠模式(靜態工廠,簡單工廠,抽象工廠):

Java反射+簡單工廠模式總結

工廠方法模式總結

抽象工廠模式和三種工廠的對比總結

不由發問了:抽象工廠模式就是策略模式吧?只不過這個策略是個簡單工廠而已?

策略模式和工廠模式的對比

兩個模式比較重要的一個區別在於:工廠模式是建立型的設計模式,偏重於建立不一樣的對象,而策略模式是行爲型的設計模式,偏重於經過不一樣的方式實現一樣的行爲。

反過來,建立對象本用的就是一個方法,經過不一樣的工廠動態指定實際的建立方法,就是封裝了這個方法,工廠模式的目的最終是爲了建立對象,而策略模式的目的並不只限於建立對象,能夠說工廠模式應用了策略模式更好地說法是,抽象工廠模式和策略模式都應用了面向接口編程的思想。

經過上面的示例能夠看出,策略模式僅僅封裝算法,提供新的算法插入到已有系統中,以及能夠把不須要的算法從系統中除去,策略模式自己不決定在什麼時候使用何種算法,在什麼狀況下使用什麼算法是由客戶端決定的。

策略模式的一個很重要的特色:運行時策略的惟一性——也就是策略模式運行期間,客戶端在每個時刻只能使用一個具體的策略實現,雖然能夠動態地在不一樣的策略實現中切換,但同時只能使用一個。

再看一個例子:購物結帳問題,收銀系統根據不一樣的折扣活動,獲得商品的不一樣價格

public interface Strategy {
    /**
     * 花費的價錢*/
    double cost(double num);
}

public class StrategyA implements Strategy {
    /**
     * 打8折*/
    @Override
    public double cost(double num) {
        return num * 0.8;
    }
}

public class StrategyB implements Strategy {
    /**
     * 9折*/
    @Override
    public double cost(double num) {
        return num * 0.9;
    }
}

public class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public double costs(double num) {
        return this.strategy.cost(num);
    }
}

public class MainDemo {
    public static void main(String[] args) {
        double num = 200;
        Context context = new Context(new StrategyB());
        double lastNum = context.costs(num);

        System.out.println(lastNum);// 180.0
    }
}

策略的注入方式

前面的例子,環境類都是在構造器裏注入的策略實現,固然也能夠不使用構造器注入,而使用setter方法注入具體的算法實現

爲何有人實現策略模式用抽象類而不是接口,是無所謂仍是另有緣由?

這就要說到常常見到的一種場景:有時候具體策略類有一些公有的行爲、屬性,這時候就應把這些公有的行爲、屬性統一提取放到共同的抽象策略角色裏面。固然這時候抽象策略角色必需要用抽象類來實現。這其實也是典型的將代碼向繼承等級結構的上方集中的標準作法。

什麼場景要使用策略模式?究竟使用策略模式有什麼實際的好處呢?

有一個例子,如圖,人須要出去旅遊,出門趕路的方式有,騎自行車去,開汽車去,直接作火車,或者飛機去……咱們應該能想到這是策略模式的一個應用場景。

兩種實現代碼,第一種實現使用了策略模式:

Person person = new Person(new Bike()); 
person.travel();

person = new Person(new Car());
person.travel();

第二種實現方式,不用策略模式,以下:

Bike bike = new Bike();
bike.travel();

Car car = new Car();
car.travel();

先理解接口的意義,參考:什麼時候使用接口(抽象類)?

論規模,這個場景其實更相似是多態,回答問題以前,首先要明白一個道理——究竟是 「人」 旅遊,仍是火車、汽車、自行車、飛機這些交通工具旅遊?

若是採用第二種實現方式,那旅遊這件事就和人沒有關係了,只和交通工具備關。正常來講,應該是人選擇xxx交通工具去xxx地旅遊,而不是直接讓交通工具去本身選本身……人選交通工具,又有這麼多工具能夠選擇,毫無疑問交通工具是易於變化的,因此把對應的交通工具嵌入到人這個類是合理的。

再來看第二種作法的壞處,咱們來把場景詳細說下:如今有兩我的(Person),分別是臺灣的小明,和上海的老王,他們是老同窗,相約去北京旅遊,他們能夠經過火車、飛機和汽車這三種不一樣的交通工具到達北京見面,到北京以後,兩我的同時經過自行車到達天安門和另外一個老同窗小麗見面……如今把程序擴展——旅行結束後,每一個人旅行的總用時都要記錄下來,用程序實現之。

若是是第二種作法,僅僅是經過調用 car.travel(),plane.travel(),train.travel() 和 bike.Travel() 方法的話,如何記錄不一樣人旅行的用時?也許強行的經過不一樣的交通工具對象來記錄,可是假如兩人到達北京之後,小明經過自行車到天安門,而老王經過汽車car臨時又去了故宮……此時,記錄每一個人旅行總用時更加麻煩了,由於總用時是跟每一個人,而不是某種交通工具備關的,因此這就是爲何要使用 person.travel() 的緣由,就是說咱們要明白某種行爲(操做)的主體是什麼。

面向對象的思想實際上就是將把現實世界中的萬事萬物當作是對象,並進行抽象,而後以計算機世界的方式對現實世界進行模擬。若是對現實世界的抽象錯誤,那麼在以後就會碰到一系列很彆扭,甚至沒法解決的問題。

策略模式的優勢就在於替換或是增長用於實現相同的功能的算法很是方便(基於多態這個特性)。爲了達到這樣的優勢,「面向接口編程」 是必須的另外,策略模式通常強調行爲,不關注不一樣的策略類自己的屬性,也就是說對於圖片中的例子,咱們是不區分不一樣的 plane,bus 等交通工具對象的,咱們須要的只是各個交通工具類的 travel() 方法。

總結——使用策略模式的場景:

一、當系統中相互獨立的算法較多

二、這些算法的功能都有共同接口

三、這些算法之後可能發生改變(增刪改等)

四、須要隱藏算法的實現

五、若是不用策略模式,會出現大量if-else的時候……

若是知足如上條件,就能夠考慮使用策略模式,如今麻煩點,之後就方便點,減小工做量,提升系統彈性

什麼場景不適用策略模式

前面明確說到:必須是相互獨立的算法…… 且策略模式的特色就是運行時算法的惟一性。故,簡單說,若是一個操做的完成不是依靠一個算法,而是複合了多個算法,或者嵌套多個算法才能完成,此時不能使用策略模式。若是偏要使用,能夠把算法粒度變粗,整合爲一個算法,仍然能使用策略模式。可是最好的解決方案,是使用裝飾模式,參考:對複合(協做)算法/策略的封裝方法——裝飾模式總結

策略模式的優勢小結

策略模式關注的不是如何實現算法,而是如何調用這些算法,它的優勢有:

一、策略模式提供了管理相關的平等的算法族的辦法

策略類的等級結構定義了一個算法族。恰當使用繼承能夠把公共的代碼移到父類裏面,從而避免重複的代碼。策略模式一個很大的特色就是各個策略算法的平等性。對於一系列具體的策略算法,你們的地位是徹底同樣的,正由於這個平等性,才能實現算法之間相互替換。全部的策略算法在實現上也是相互獨立的,因此能夠這樣描述這一系列策略算法:策略算法是相同行爲的不一樣實現

二、策略模式提供了能夠替換繼承關係的辦法(聚合抽象的引用)

繼承能夠處理多種算法或行爲。若是不用策略模式,那麼使用算法或行爲的環境類就可能會有一些子類,每個子類提供一個不一樣的算法或行爲。可是,這樣一來算法或行爲的使用者就和算法或行爲自己混在一塊兒。決定使用哪種算法或採起哪種行爲的邏輯就和算法或行爲的邏輯混合在一塊兒,從而不可能再獨立演化。繼承的缺陷就是使得動態改變算法或行爲變得不可能

三、使用策略模式能夠避免使用多重 if 語句

多重 if 語句不易維護,它把採起哪種算法或採起哪種行爲的邏輯與算法或行爲的邏輯混合在一塊兒,通通列在一個多重轉移語句裏面,比使用繼承的辦法還要原始和落後

說了那麼多,策略模式就沒有缺點麼?

一、前面咱們發現要想使用策略模式,客戶端(調用者)必須清楚全部的策略都是什麼,才能決定使用哪個策略,也就是說,客戶端必須理解全部算法的不一樣,才能適時的選擇合適的算法,若是客戶端不清楚以上,那麼策略模式不適用。

二、策略模式策略類膨脹的問題,這一度被認爲是策略模式的陰暗面。由於不管是爲了解決多重 if-else 語句,仍是解決 switch case 分支過多,而且擴展性差的問題,一旦可替換的策略(算法)愈來愈多,雖然擴展性獲得解決,可是策略(算法)類太多了,每個分支對應一個……又很是繁瑣,這就是它的一個缺點。換句話說,策略模式形成不少的策略類。

怎麼解決策略類膨脹的問題?

有時候能夠經過把依賴於環境類的狀態保存到客戶端裏面,而將策略類設計成可共享的,這樣策略類實例能夠被不一樣客戶端使用。換言之,可使用享元模式來減小對象的數量。下面就趁熱打鐵,說一說享元模式——減少內存的佔用問題——享元模式和單例模式的對比分析 

JDK裏使用了策略模式的類都有哪些?

javax.servlet.http.HttpServlet 類:

HTTP 請求實際上只是一個 HTTP 請求報文,Web 容器會自動將這個 HTTP 請求報文包裝成一個 HttpServletRequest 對象,而且自動調用 HttpServlet的 service() 方法來解析這個HTTP請求,service()方法會解析HTTP請求行,而HTTP請求行由method,URI,HTTPVersion三個組成,method就是get或者post,service() 方法根據 method 來決定是執行 doGet 仍是 doPost,這一切都是服務器(容器)自動完成的,HTTP的格式也自動被解析。

只要自定義的類繼承了HttpServlet,而且在web.xml裏面配置了相應的servlet和mapping,服務器就會自動執行以上過程。而咱們自定義的每個Servlet都必需要實現Servlet接口,而圖裏的GenericServlet是個通用的、不特定於任何協議的Servlet,它實現了 Servlet 接口 

而 HttpServlet 繼承於 GenericServlet,所以 HttpServlet 也實現了 Servlet 接口,因此咱們定義的Servlet只須要繼承HttpServlet父類便可。Servlet接口中定義了一個service方法:

View Code

HttpServlet對該方法進行了實現,實現方式就是將ServletRequest與ServletResponse轉換爲HttpServletRequest與HttpServletResponse。轉換完畢後,會調用HttpServlet類中本身定義的service方法

View Code

發現下面這個 service 是 protected方法

View Code

在該轉換以後的 service 方法中,首先得到到請求的方法名,而後根據方法名調用對應的doXXX方法,好比說請求方法爲GET,那麼就去調用doGet方法;請求方法爲POST,那麼就去調用doPost方法。好比:

doPut方法
doGet方法

在HttpServlet類中所提供的doGet、doPost……方法都是直接返回錯誤信息,因此咱們須要在本身定義的Servlet類中重寫這些方法,通過上面的過程,咱們發現 HttpServlet 類就是一個策略抽象類,咱們本身定義的servlet類去覆蓋HttpServlet裏的這些方法,天然不一樣的人,不一樣的項目裏,須要重寫的內容和方法都不盡相同,這就是一個策略模式的思想。

相關文章
相關標籤/搜索