1. 程式人生 > >22】之策略模式(Strategy)

22】之策略模式(Strategy)

1      模式簡介

在策略模式中,一個類的行為或其演算法可以在執行時改變。策略模式定義了一系列演算法,把它們一個個封裝起來,並且使它們可以互相替換。

策略模式的優點:

1)        演算法可以自由切換;

2)        避免使用多重條件判斷;

3)        擴充套件性良好。

策略模式的缺點:

1)        演算法可以自由切換;

2)        避免使用多重條件判斷;

3)        擴充套件性良好。

策略模式的適用場景:

1)        當一個系統中有許多類,它們之間的區別僅在於它們的行為,希望動態地讓一個物件在許多行為中選擇一種行為時;

2)        當一個系統需要動態地在幾種演算法中選擇一種時;

3)        當一個物件有很多的行為,不想使用多重的條件選擇語句來選擇使用哪個行為時。

2      案例

在這個例子中,我們來模擬一個遊戲:這個遊戲中有各種鴨子,它們會飛,也會叫,但它們飛和叫的方式不同,如紅頭鴨是呱呱叫的,而橡皮鴨是吱吱叫的;紅頭鴨是用翅膀飛的,而橡皮鴨是不會飛的。我們希望用JAVA程式語言結合策略模式來完成這個功能。

2.1 使用繼承

鴨子雖然有不同的種類,但是也有一定的相同之處,所以我們可以從鴨子中提取一個父類Duck,讓不同種類的鴨子類繼承自父類,將所有鴨子共有的屬性和行為放到父類中。這種思想對應的類圖如下:

這樣做看似沒有錯誤,但實際上存在以下兩個方面的錯誤:

1、子類的可用行為可能遠大於我們的期待範圍。把所有方法和屬性都定義在Duck父類中,再由所有子類去繼承這個父類,那麼所有的子類就都具備父類的方法和屬性。就拿這個遊戲的例子來說,如果加入一個方法fly(),那麼就算是橡皮鴨(RubberDuck)類也一定會具備fly()的行為。

2、這種方法並不能有效的解決程式碼重複問題。拿上一條中的fly()方法來說,既然不同的鴨子具有不同的飛行方式,則fly()方法的方法體一定是寫在子類中;而不同種類的鴨子的飛行方式也可能是相同的,所以我們就可能不得不在多個鴨子中使用相同的fly()方法,而在另外一些鴨子中使用另一種fly()方法。

2.2 增加介面

我們可以把Duck父類中的fly()方法提取出來定義成介面,稱為Flyable,其中定義一個抽象方法fly(),讓可以飛的Duck子類實現這個介面並實現fly()方法。各個類中的行為細節及類間關係如下圖所示。


使用介面有效的解決了上面的第一個問題,即可以有效的控制各個子類的行為範圍,不會出現“子類具有父類的所有方法”的情況了。但是,JAVA中的介面中不具備程式碼實現功能,所以這樣做並沒有解決程式碼重複的問題。

2.3 解決問題

我們需要找出應用中可能需要變化的地方,把它們獨立出來,不要和那些不需要變化的程式碼混在一起,即把會變化的部分取出並封裝起來,一邊以後可以輕易的改動或擴充套件此部分,而不影響不需要變化的部分。

在上面的兩次嘗試(使用繼承、增加介面)中,我們都只是把共有的屬性和行為抽出來封裝成父類或介面,但在這裡,我們要儘量兩次封裝,即在業務類上面還有兩層封裝類。對於一種行為,我們首先將這種行為抽取出來作為一個總的行為介面,然後再在這個介面下面定義多個不同的實現類,最後再在業務類中呼叫父類完成業務。簡單的說,在上面兩種嘗試中,我們只是定義了兩個互不相關的行為,而在這種方法中,我們定義的是一組有關聯的行為。

拿我們這個例子中的fly()方法舉例,飛,可能是用翅膀飛(FlyWithWings),也可能是坐火箭飛(FlyRocketPower)。因此,我們定義一個總的介面FlyBehavior,在這個介面中定義一個抽象方法fly()。再定義兩個實現FlyBehavior介面的類FlyWithWings和FlyRocketPower,這兩個類實現FlyBehavior介面,並實現介面中的fly()方法。另外,我們在Duck父類中定義一個FlyBehavior的介面變數,在生成鴨子實體的時候,給其FlyBehavior賦值,以此決定鴨子的飛行方式。我們還可以在Duck父類中定義一個設定飛行方式的方法setFlyBehavior(),來動態的改變鴨子的飛行方式。以下是類圖:


以下貼出對這個問題的解決方案的程式碼:

飛行介面FlyBehavior中的程式碼:
public interface FlyBehavior {
	// 飛行的抽象方法
	void fly();
}
使用翅膀飛行類FlyWithWings中的程式碼:
public class FlyWithWings implements FlyBehavior {

	@Override
	public void fly() {
		System.out.println("使用翅膀飛行");
	}
}
其實還有不會飛的類FlyNoWay,這裡就不貼出來了。 叫的介面QuackBehavior中的程式碼:
public interface QuackBehaviro {
	// 叫的抽象方法
	void quack();
}
呱呱叫的類Quack中的程式碼:
public class Quack implements QuackBehaviro {

	@Override
	public void quack() {
		System.out.println("呱呱叫");
	}
}
其實還有吱吱叫的類Squeak,這裡就不貼出來了。 鴨子的父類Duck中的程式碼:
public class Duck {
	protected FlyBehavior flyBehavior;
	protected QuackBehaviro quackBehaviro;

	public void performFly() {
		flyBehavior.fly();
	}

	public void performQuack() {
		quackBehaviro.quack();
	}

	public void setFlyBehavior(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	public void setQuackBehaviro(QuackBehaviro quackBehaviro) {
		this.quackBehaviro = quackBehaviro;
	}
}
鴨子的一個子類紅頭鴨RedHeadDuck中的程式碼:
public class RedHeadDuck extends Duck {

	public RedHeadDuck() {
		System.out.println("只是一隻紅頭鴨");
		super.flyBehavior = new FlyWithWings();
		super.quackBehaviro = new Quack();
	}
}
鴨子的另一個子類橡皮呀RubberDuck中的程式碼:
public class RubberDuck extends Duck {

	public RubberDuck() {
		System.out.println("這是一隻橡皮鴨");
		super.flyBehavior = new FlyNoWay();
		super.quackBehaviro = new Squeak();
	}
}
測試類Test中的程式碼:
public class Test {
	public static void main(String[] args) {
		Duck redHeadDuck = new RedHeadDuck();
		redHeadDuck.performFly();
		redHeadDuck.performQuack();

		System.out.println();

		Duck rubberDuck = new RubberDuck();
		rubberDuck.performFly();
		rubberDuck.performQuack();
	}
}
執行結果如下圖所示: