公號:碼農充電站pro
主頁:https://codeshellme.github.iohtml
本篇來介紹策略模式(Strategy Design Pattern)。java
假設咱們要爲動物進行建模,好比狗,豬,兔子等,每種動物的能力是不一樣的。git
首先你可能想到用繼承的方式來實現,因此咱們編寫了下面這個 Animal
類:github
abstract class Animal { public void run() { System.out.println("I can run."); } public void drinkWater() { System.out.println("I can drink water."); } protected abstract String type(); }
Animal
是一個抽象類,其中包括了動物的能力,每種能力用一個方法表示:算法
而後咱們編寫 Dog
,Pig
和 Rabbit
:shell
class Dog extends Animal { public String type() { return "Dog"; } } class Pig extends Animal { public String type() { return "Pig"; } } class Rabbit extends Animal { public String type() { return "Rabbit"; } }
上面的三種動物都繼承了 Animal
中的 run 和 drinkWater,而且都實現了本身的 type 方法。設計模式
如今咱們想給 Pig
和 Rabbit
加入吃草
的能力,最直接的辦法是分別在這兩個類中加入 eatGrass 方法,以下:函數
class Pig extends Animal { public void eatGrass() { System.out.println("I can eat grass."); } public String type() { return "Pig"; } } class Rabbit extends Animal { public void eatGrass() { System.out.println("I can eat grass."); } public String type() { return "Rabbit"; } }
上面代碼可以達到目的,可是不夠好,由於Pig
和 Rabbit
中的 eatGrass 如出一轍,是重複代碼,代碼沒能複用。測試
爲了解決代碼複用,咱們能夠將 eatGrass 方法放到 Animal
中,利用繼承的特性,Pig
和 Rabbit
中就不須要編寫 eatGrass 方法,而直接從 Animal
中繼承就行。this
可是,這樣仍是有問題,由於若是將 eatGrass 放在 Animal
中,Dog
中也會有 eatGrass ,而咱們並不想讓 Dog
擁有吃草
的能力。
也許你會說,咱們能夠在 Dog
中將 eatGrass 覆蓋重寫,讓 eatGrass 不具備實際的能力,就像這樣:
class Dog extends Animal { public void eatGrass() { // 什麼都不寫,就沒有了吃草的能力 } public String type() { return "Rabbit"; } }
這樣作雖然能到達目的,可是並不優雅。若是 Animal
的子類特別多的話,就會有不少子類都得這樣覆蓋 eatGrass 方法。
因此,將 eatGrass 放在 Animal
中也不是一個好的方案。
那是否能夠將 eatGrass 方法提取出來,做爲一個接口?
就像這樣:
interface EatGrassable { void eatGrass(); }
而後,讓須要有吃草
能力的動物都去實現該接口,就像這樣:
class Rabbit extends Animal implements EatGrassable { public void eatGrass() { System.out.println("I can eat grass."); } public String type() { return "Rabbit"; } }
這樣作能夠達到目的,可是,缺點是每一個須要吃草
能力的動物之間就會有重複的代碼,依然沒有達到代碼複用的目的。
因此,這種方式仍是不能很好的解決問題。
咱們能夠將吃草
的能力看做一種「行爲」,而後使用「行爲類」來實現。那麼須要有吃草
能力的動物,就將吃草類
的對象,做爲本身的屬性。
這些行爲類就像一個個的組件,哪些類須要某種功能的組件,就直接拿來用。
下面咱們編寫「吃草類」:
interface EatGrassable { void eatGrass(); } class EatGreenGrass implements EatGrassable { // 吃綠草 public void eatGrass() { System.out.println("I can eat green grass."); } } class EatDogtailGrass implements EatGrassable { // 吃狗尾草 public void eatGrass() { System.out.println("I can eat dogtail grass."); } } class EatNoGrass implements EatGrassable { // 不是真的吃草 public void eatGrass() { System.out.println("I can not eat grass."); } }
首先建立了一個 EatGrassable
接口,可是不用動物類來實現該接口,而是咱們建立了一些行爲類 EatGreenGrass
,EatDogtailGrass
和 EatNoGrass
,這些行爲類實現了 EatGrassable
接口。
這樣,須要吃草的動物,不但可以吃草,並且能夠吃不一樣種類的草。
那麼,該如何使用 EatGrassable
接口呢?須要將 EatGrassable
做爲 Animal
的屬性,以下:
abstract class Animal { // EatGrassable 對象做爲 Animal 的屬性 protected EatGrassable eg; public Animal() { eg = null; } public void run() { System.out.println("I can run."); } public void drinkWater() { System.out.println("I can drink water."); } public void eatGrass() { if (eg != null) { eg.eatGrass(); } } protected abstract String type(); }
能夠看到,Animal
中增長了 eg
屬性和 eatGrass
方法。
其它動物類在構造函數中,要初始化 eg
屬性:
class Dog extends Animal { public Dog() { // Dog 不能吃草 eg = new EatNoGrass(); } public String type() { return "Dog"; } } class Pig extends Animal { public Pig() { eg = new EatGreenGrass(); } public String type() { return "Pig"; } } class Rabbit extends Animal { public Rabbit() { eg = new EatDogtailGrass(); } public String type() { return "Rabbit"; } }
對代碼測試:
public class Strategy { public static void main(String[] args) { Animal dog = new Dog(); Animal pig = new Pig(); Animal rabbit = new Rabbit(); dog.eatGrass(); // I can not eat grass. pig.eatGrass(); // I can eat green grass. rabbit.eatGrass(); // I can eat dogtail grass. } }
實際上,上面的實現方式使用的就是策略模式。重點在於 EatGrassable
接口與三個行爲類 EatGreenGrass
,EatDogtailGrass
和 EatNoGrass
。在策略模式中,這些行爲類被稱爲算法族,所謂的「策略」,能夠理解爲「算法」,這些算法能夠互相替換。
策略模式定義了一系列算法族,並封裝在類中,它們之間能夠互相替換,此模式讓算法的變化獨立於使用算法的客戶。
我將完整的代碼放在了這裏,供你們參考,類圖以下:
在一開始的設計中,咱們使用的是繼承(Is-a) 的方式,可是效果並非很好。
最終的方案使用了策略模式,它是一種組合(Has-a) 關係,即 Animal
與 EatGrassable
之間的關係。
這也是一種設計原則:多用組合,少用繼承,組合關係比繼承關係有更好的彈性。
策略模式不只重在建立一組算法(行爲類),可以動態的讓這些算法互相替換,也是策略模式典型應用。
所謂的「動態」是指,在程序的運行期間,根據配置,用戶輸入等方式,動態的設置算法。
只須要在 Animal
中加入 setter
方法便可,以下:
abstract class Animal { // 省略了其它代碼 public void setEatGrassable(EatGrassable eg) { this.eg = eg; } }
使用 setter
方法:
Animal pig = new Pig(); pig.eatGrass(); // I can eat green grass. pig.setEatGrassable(new EatDogtailGrass()); // 設置新的算法 pig.eatGrass(); // I can eat dogtail grass.
原本 pig
吃的是綠草
,咱們經過 setter
方法將 綠草
換成了 狗尾草
,能夠看到,算法的切換很是方便。
策略模式定義了一系列算法族,這些算法族也能夠叫做行爲類。策略模式使用了組合而非繼承來構建類之間的關係,組合關係比繼承關係更加有彈性,使用組合也比較容易動態的改變類的行爲。
(本節完。)
推薦閱讀:
歡迎關注做者公衆號,獲取更多技術乾貨。