1. 程式人生 > >【設計模式】HeadFirst設計模式(一):策略(Strategy)模式

【設計模式】HeadFirst設計模式(一):策略(Strategy)模式

1、概述

在軟體開發的過程中,實現一個功能可能會使用很多種演算法,一種比較常用的做法是把這些演算法或者策略寫在一個類中,一個演算法寫一個方法。當我們需要新增一個演算法的時候,我們需要修改封裝這些演算法的類,並且在客戶端也可能需要修改相應的實現。如果一個專案需要很多的演算法的情況下,這樣的設計會增加專案的維護複雜度。

2、問題

如何讓演算法獨立於使用它的客戶而獨立變化

3、定義

策略模式:定義了演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶

4、設計原則

把一個類中經常改變或者將來可能改變的部分提取出來,作為一個介面(也可以是抽象類,java中的abstract),然後在

這個類中包含這個介面(虛類)物件的例項,這樣類的例項在執行時就可以隨意呼叫實現了這個介面的類的行為。

5、適用環境

因為策略模式主要針對一演算法,將每一個演算法封裝到具有共同介面的獨立的類中,從而使得它們可以相互替換。策略模式使得演算法可以在不影響 到客戶端的情況下發生變化。通常,策略模式適用於當一個應用程式需要實現一種特定的服務或者功能,而且該程式有多種實現方式時使用

6、結構

三個物件:

(1) 環境物件: 持有抽象策略物件的引用,供給客戶端直接呼叫

(2)抽象策略物件:策略類,由介面或者抽象類實現

(3)具體策略物件:包裝了相關演算法和行為

UML類圖:

7、應用場景——模擬鴨子

背景: Joe上班的公司做了一套鴨子游戲,遊戲中有各種的鴨子:一邊游泳,一邊呱呱叫。設計了一個鴨子超類,讓各種鴨子繼承此超類


Duck抽象類:quack()方法(已經實現),swim()方法(已經實現),display()方法是抽象方法

MallardDuck類:display()方法描述“鴨子是綠頭”

RedheadDuck類:display()方法描述“鴨子是紅頭”

問題一:老闆決定開發一種能夠飛的鴨子出來,把競爭者拋在後頭。需要怎麼設計?

這是很簡單的事情嘛,只需要在 Duck 超類中增加一個 fly() 方法就可以了。這個時候問題出來了,所有的鴨子都繼承了 fly() 方法,導致了橡皮鴨子在天上飛(橡皮鴨子不會飛)

所以,我們要在橡皮鴨子類(RubberDuck)中把 fly() 方法覆蓋了,讓什麼事情都不做,並且把 quack() 方法覆蓋了,讓“鴨子呱呱叫” 變成 “鴨子吱吱叫”


我們看到新增加了一個類 RubberDuck 橡皮鴨子類:

quack()方法:覆蓋成吱吱叫

display()方法:外觀是橡皮鴨

fly()方法:覆蓋,變成什麼事情都不做

問題二:如果,我們要新增加一個 誘餌鴨DecoyDuck 類(不會飛也不會叫)該怎麼辦?

按照上邊繼承的做法:把quack()方法覆蓋成什麼也不做,把fly()方法覆蓋成什麼也不做,把display()方法實現為:外觀為誘餌鴨

問題三:主管說,公司每過6個月要更新一次產品。Joe知道規格會經常改變,每當有新的鴨子子類出現的時候,他就要被迫檢查並可能需要覆蓋fly()和quack()方法。由此可見,繼承並不是答案,Joe需要怎麼辦呢?

Joe想到了如下的解決方案:


Flyable介面:放一個fly()方法

Quackable介面:放入quack()方法

Duck抽象類(或介面):放入display()和swim()方法,因為所有鴨子都會這兩個方法

MallardDuck類:綠頭鴨子,display()表現為綠頭,fly()表現為會飛,quack()表現為呱呱叫

RedheadDuck類:紅頭鴨子,display()表現為紅頭,fly()表現為會飛,quack()表現為呱呱叫

RubberDuck類:橡皮鴨子,display()表現為橡皮鴨,quack()表現為 吱吱叫 (因為不會飛所以沒有實現Flyable介面)

DecoyDuck類:誘餌鴨, display()表現為誘餌鴨 (因為不會飛不會叫所以不實現兩個介面)

問題三:這個主意真的太笨了,如果這樣設計的話重複的程式碼會變多。如果48個Duck的子類都要稍微修改一下飛行的行為,那麼在48只鴨子類中的fly()方法都要實現

設計原則:把應用中可能需要變化的地方獨立出來,不要和不需要變化的程式碼混合在一起(如果每次有新的需求,都會使某方面程式碼發生變化,那麼久可以確定這部分程式碼需要被抽取出來)

接下來,我們就要設計鴨子了

(1) 分開變化的和不會變化的部分

變化的部分:鴨子的飛行行為和叫的行為

不變的部分:鴨子的描述

所以,我們可以得到下邊的UML圖:


下邊我們看一下具體的實現:

FlyBehavior介面

public interface FlyBehavior {
	void fly();
}

飛行類FlyWithWings.java 實現FlyBehavior
public class FlyWithWings implements FlyBehavior {
	public void fly() {
		System.out.println("I'm flying!");
	}
}

飛行類FlyNoWay.java實現FlyBehavior
public class FlyNoWay implements FlyBehavior{
	public void fly(){
		System.out.println("I can't fly.");
	}
}
QuackBehavior介面
public interface QuackBehavior {
	void quack();
}
Quack類,實現QuackBehavior
public class Quack implements QuackBehavior{
	public void quack(){
		System.out.println("quack");
	}
}
Squeak類,實現QuackBehavior
public class Squeak implements QuackBehavior{
	public void quack(){
		System.out.println("Squeak");
	}
}
MuteQuack類,實現QuackBehavior
public class MuteQuack implements QuackBehavior{
	public void quack(){
		System.out.println("<<Silence>>");
	}
}
Duck抽象類:
public abstract class Duck {
	protected FlyBehavior flyBehavior;
	protected QuackBehavior quackBehavior;
	
	public 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 void setFlyBehavior(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	public void setQuackBehavior(QuackBehavior quackBehavior) {
		this.quackBehavior = quackBehavior;
	}
}

我們建立一個DecoyDuck誘餌鴨類(不會飛不會叫)
public class DecoyDuck extends Duck {
	//構造方法,初始化確定誘餌鴨不會飛不會叫
	public DecoyDuck() {
		flyBehavior = new FlyNoWay();
		quackBehavior = new MuteQuack();
	}
	//描述方法,得自Duck類
	public void display() {
		System.out.println("I'm a decoy duck.");
	}
	//改變鴨子行為:會飛會叫
	public void changeBehavior() {
		setFlyBehavior(new FlyWithWings());
		setQuackBehavior(new Quack());
	}
}