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


一.什么是策略模式(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.算法是獨立於客戶代碼的,也就是對算法封裝的具體體現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM