跳到主要內容
黯羽輕揚每天積累一點點

設計模式之策略模式(Strategy Pattern)

免費2015-03-06#Design_Pattern#策略模式#Strategy Pattern

設計模式是由程式碼結構優化經驗萃取出來的理論知識,應用成熟的設計模式能夠增強程式碼的可複用性、可擴充性與可維護性。其中,策略模式是最基礎的設計模式之一,簡單地說,策略模式就是把可以替換的演算法步驟封裝成一個個演算法族,供執行時動態選擇。

no_mkd

一.什麼是策略模式(Strategy Pattern)?

從字面上理解,策略模式就是應用了某種“策略”的設計模式,而這個“策略”就是:把變化的部分封裝起來。

其實這個理解有誤,例子沒有大錯,但對此模式的理解存在偏差,修改內容已經追加在本文尾部,點我跳轉>>

二.舉個例子

假定現在我們需要用類別來描述 Dog,首先,所有的 Dog 都有外形(比如 Color),有行為(比如 Run),於是我們很自然地定義了這樣一個基類 Dog:

public abstract class Dog {
	public abstract void display();//顯示 Dog 的外形
	public abstract void run();//定義 Dog 的 Run 行為
}

其它的 Dog 具體類別全部繼承自 Dog 基類就好了,很快,需求來了:有一隻 Red Dog,它是寵物犬,跑得很慢,叫聲也很小。於是我們定義了 RedDog 類別:

public class RedDog {
	 @override
	public void display(){
		System.out.println("this is a red dog.");
	}
	 @override
	public void run(){
		System.out.println("it's running slowly.");
	}
}

接著,來了一大批需求,Black Dog、Gray Dog、Pink Dog、BlackGrayDog、PinkGrayDog。。。一共 100 隻 Dog,除 Color 不同外,Run 的速度也不同。於是我們定義了 100 個類別來表示 100 種不同類型的 Dog,每個類別中都實現了 Run 方法與 Display 方法。

嗯~,看看我們的類別圖,沒錯,這個 BigBang 就是我們的類別圖了,好像很合理啊,有 100 種 Dog 當然會有 100 個類別啊,而且具體 Dog 類別當然要繼承自 Dog 基類吧?嗯,好像是這樣的,而且這樣好像也沒有什麼不好,至少現在看不出來有什麼不妥。。。

又一個新的需求來了,我們發現 Dog 不僅可以 Run,還可以 Bark。自然而然地,我們想到了給 Dog 基類定義一個新行為 Bark,然後在 100 個具體類別中逐一重寫了 Bark 方法。。。雖然過程有些麻煩,不過好在我們還是解決了問題,現在所有的 Dog 都可以 Bark 了

這天,來了一隻新 Dog,它叫 ToyDog,是玩具狗,不會 Run 也不能 Bark,只是個玩偶。於是我們讓 ToyDog 繼承了 Dog 基類,重寫了 Run 方法和 Bark 方法(方法體為空,因為 Toy 不會 Run 也不會 Bark)。問題好像也被完美解決了,至少現在看來不存在什麼問題

很多天後,ToyDog 工廠技術革新了,新產品被裝上了電池,可以 Bark 了。沒關係,我們修改了 ToyDog 類別,實現了 Bark。我們初始化了一隻 ToyDog,它歡快的 BarkBarkBark,過了一會兒,電池沒電了,ToyDog 不能 Bark 了,但是我們的 ToyDog 類別顯示它還可以 Bark。。。

現在,終於出問題了吧,我們發現 Dog 問題中最難處理的部分就是 Run、Bark 這兩個行為,一旦發生變化我們就需要修改具體 Dog 類別,甚至是 Dog 基類,不僅需要花費大量的時間,而且所有具體 Dog 類別中都實現了 Run 與 Bark 方法,顯得很臃腫。還有最重要的問題,我們無法動態地修改 Dog 的行為,比如小 Dog 跑得慢叫聲小,長大後跑得快叫聲大,也無法應對上面提到的電池沒電導致的行為變化問題。。。。這一系列的問題想向我們說明一點:這從一開始就是一個糟糕的設計。

三.如何應用策略模式?

策略模式要求把變化的部分封裝起來,首先,我們要找到的就是程式碼中頻頻發生變化的部分。在上一個例子中,變化的部分是什麼?

1.Run 行為;2.Bark 行為;3.其它可能存在的行為

下面我們把這些行為封裝起來(以 Run 為例):

package StrategyPattern;

/**

  • @author ayqy
  • 定義 Run 介面

*/ public interface IRun { public void run();//定義 run 行為 }

package StrategyPattern;

/**
 * @author ayqy
 * 實現 RunQuickly 行為
 *
 */
public class RunQuickly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running quicky.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 實現 RunSlowly 行為
 *
 */
public class RunSlowly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running slowly.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 實現 RunNoWay 行為
 *
 */
public class RunNoWay implements IRun{

	 @Override
	public void run() {
		System.out.println("it isn't able to run.");
	}

}

Bark 行為的封裝與之類似。封裝好“變化”之後,我們的 Dog 基類也要做相應改變:

package StrategyPattern;

/**

  • @author ayqy
  • 定義 Dog 基類

*/ public abstract class Dog { IBark bark; IRun run;

public abstract void display();//顯示 Dog 的外形

public IBark getBark() {
	return bark;
}

public void setBark(IBark bark) {
	this.bark = bark;
}

public IRun getRun() {
	return run;
}

public void setRun(IRun run) {
	this.run = run;
}

}

注意,我們只封裝了容易發生變化的部分(Bark 與 Run),而沒有封裝 Display 方法(Dog 的外形應該比較 fixed 吧),什麼才是“變化的部分”?這需要我們仔細考慮,視具體情景而定

現在來看看我們新的具體 Dog 類別吧

package StrategyPattern;

/**

  • @author ayqy
  • 實現 RedDog

*/ public class RedDog extends Dog{

public RedDog()
{
	//紅狗是寵物狗
	//跑得慢,叫聲小
	super.setRun(new RunSlowly());
	super.setBark(new BarkQuietly());
}

 @Override
public void display() {
	System.out.println("this is a red dog.");
}

}

類別被明顯瘦身了吧,而且我們現在還能在執行時動態地修改 Dog 的行為,看到策略模式的威力了吧。

四.效果示例

編寫一個測試類別:

package StrategyPattern;

/**

  • @author ayqy
  • 測試策略模式<br/>
  • 策略模式,簡單的說就是要求把“變化”封裝起來,以隔離“變化”對其它部分的影響

*/ public class Test {

/**
 * @param args
 */
public static void main(String[] args) {
	System.out.println("------- 創造一隻 Red Dog -------");
	Dog redDog = new RedDog();
	showDogInfo(redDog);
	
	System.out.println("\n------- 創造一隻 Black Dog -------");
	Dog blackDog = new BlackDog();
	showDogInfo(blackDog);
	
	System.out.println("\n------- 創造一隻 Toy Dog -------");
	Dog toyDog = new ToyDog();
	showDogInfo(toyDog);
	
	System.out.println("\n------- 技術革新,現在 Toy Dog 可以小聲叫了 -------");
	toyDog.setBark(new BarkQuietly());
	showDogInfo(toyDog);
	
	//上面的程式碼並不是最優的,只是為了說明策略模式
	//一個很明顯問題是 Dog redDog = new RedDog();
	//我們在面向具體物件編碼,而不是設計模式所提倡的面向介面編碼
	//可以定義一個 Dog 工廠來生產 Dog 物件以求模組之間更鬆的耦合
}

/**
 * @param dog
 * 顯示 Dog 相關資訊
 */
private static void showDogInfo(Dog dog)
{
	dog.display();
	dog.run.run();
	dog.bark.bark();
}

}

執行結果示例:

五.總結

策略模式的核心是要把頻繁發生變化的部分封裝起來,作用是把變化部分的影響隔離開,避免局部的變化對其它 fixed 部分造成影響,設計時可能需要更多的時間,但便於維護、複用與擴充,在本例中,Run、Bark 行為都可以在新的類別(如 Pig)中複用;一旦行為發生變化我們只需要修改各個行為介面,最多再對 Dog 基類做簡單修改就可以從容地應對變化了。

六.修正

上面的內容有些偏差,但勉強可以看,只是不太準確,下面作以準確的描述:

策略模式的思想確實是封裝,但這裡的”策略“並不是指”把變化封裝起來“,這是在反覆讀了幾遍原文後發現的(《Head First 設計模式》第一章感覺有點不太好,第一遍理解偏了)

這裡的”策略“近似於演算法

策略模式不太容易理解(雖然筆者極力避免照搬原文,但萬一再理解偏了就誤導別人了。。),書上的準確定義是這樣的:

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

個人反覆讀過幾遍之後,覺得原文想表達的東西是這樣的:

  • 1.策略模式核心是對演算法的封裝(還有一種設計模式叫”範本方法模式“,也是對演算法的封裝,區別與聯絡放在裡面介紹,點我跳轉>>
  • 2.專注於實現演算法(策略)的選擇,支援執行時動態改變策略
  • 3.具體實現是把變化的部分找出來,定義為介面,每個介面對應一組演算法,每一個都是一種策略
  • 4.同一介面下的演算法是可以相互替換的
  • 5.演算是獨立於客戶程式碼的,也就是對演算法封裝的具體體現

評論

暫無評論,快來發表你的看法吧

提交評論