公號:碼農充電站pro
主頁:https://codeshellme.github.io
本篇來介紹策略模式(Strategy Design Pattern)。
假設我們要為動物進行建模,比如狗,豬,兔子等,每種動物的能力是不同的。
1,使用繼承
首先你可能想到用繼承的方式來實現,所以我們編寫了下面這個 Animal
類:
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
是一個抽象類,其中包括了動物的能力,每種能力用一個方法表示:
- run:奔跑能力。
- drinkWater:喝水能力。
- type:返回動物的種類,比如“狗”,“兔子”。這是一個抽象方法,子類要去實現。
然后我們編寫 Dog
,Pig
和 Rabbit
:
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
中繼承就行。
但是,這樣還是有問題,因為如果將 eatGrass 放在 Animal
中,Dog
中也會有 eatGrass ,而我們並不想讓 Dog
擁有吃草
的能力。
也許你會說,我們可以在 Dog
中將 eatGrass 覆蓋重寫,讓 eatGrass 不具有實際的能力,就像這樣:
class Dog extends Animal {
public void eatGrass() {
// 什么都不寫,就沒有了吃草的能力
}
public String type() {
return "Rabbit";
}
}
這樣做雖然能到達目的,但是並不優雅。如果 Animal
的子類特別多的話,就會有很多子類都得這樣覆蓋 eatGrass 方法。
所以,將 eatGrass 放在 Animal
中也不是一個好的方案。
2,使用接口
那是否可以將 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";
}
}
這樣做可以達到目的,但是,缺點是每個需要吃草
能力的動物之間就會有重復的代碼,依然沒有達到代碼復用的目的。
所以,這種方式還是不能很好的解決問題。
3,使用行為類
我們可以將吃草
的能力看作一種“行為”,然后使用“行為類”來實現。那么需要有吃草
能力的動物,就將吃草類
的對象,作為自己的屬性。
這些行為類就像一個個的組件,哪些類需要某種功能的組件,就直接拿來用。
下面我們編寫“吃草類”:
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.
}
}
4,策略模式
實際上,上面的實現方式使用的就是策略模式。重點在於 EatGrassable
接口與三個行為類 EatGreenGrass
,EatDogtailGrass
和 EatNoGrass
。在策略模式中,這些行為類被稱為算法族,所謂的“策略”,可以理解為“算法”,這些算法可以互相替換。
策略模式定義了一系列算法族,並封裝在類中,它們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶。
我將完整的代碼放在了這里,供大家參考,類圖如下:
5,繼承與組合
在一開始的設計中,我們使用的是繼承(Is-a) 的方式,但是效果並不是很好。
最終的方案使用了策略模式,它是一種組合(Has-a) 關系,即 Animal
與 EatGrassable
之間的關系。
這也是一種設計原則:多用組合,少用繼承,組合關系比繼承關系有更好的彈性。
6,動態設定行為
策略模式不僅重在創建一組算法(行為類),能夠動態的讓這些算法互相替換,也是策略模式典型應用。
所謂的“動態”是指,在程序的運行期間,根據配置,用戶輸入等方式,動態的設置算法。
只需要在 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
方法將 綠草
換成了 狗尾草
,可以看到,算法的切換非常方便。
7,總結
策略模式定義了一系列算法族,這些算法族也可以叫作行為類。策略模式使用了組合而非繼承來構建類之間的關系,組合關系比繼承關系更加有彈性,使用組合也比較容易動態的改變類的行為。
(本節完。)
推薦閱讀:
歡迎關注作者公眾號,獲取更多技術干貨。