策略模式(Strategy Pattern)

 

現在我們得讓鴨子能飛è 在Duck類中加上fly()方法,然後所有鴨子都會繼承fly()。

 

但是,可怕的問題發生了……

“橡皮鴨子”在屏幕上飛來飛去,這是你在開玩笑嗎?

 

 

把“橡皮鴨子”當成一種“特色”,


 覆蓋父類的fly()方法




可是,如果以後我加入誘餌鴨(DecoyDuck) -->  木頭假鴨,不會飛也不會叫……

 

 

利用繼承來提供Duck的行爲,會導致如下行爲

 

代碼在多個子類中重複。

qB.  運行時的行爲不容易改變。

qC.  我們不能讓鴨子跳舞。

qD.  很難知道所有鴨子的全部行爲。

qE.  鴨子不能同時又飛又叫。

qF.  改變會牽一髮動全身,造成其他鴨子不想要的改變。

 

利用接口如何?

 

我可以把fly()從超類中取出來,放進一個“Flyable接口”中。這麼一來,只有會飛的鴨子才實現此接口。同樣的方式,也可以用來設計一個“Quackable接口”,因爲不是所有的鴨子都會叫。


 

 

你覺得這個設計如何?

這真是一個超笨的主意,你沒發現這麼一來重複的代碼會變多嗎?如果你認爲覆蓋幾個方法就算是差勁,那麼對於48個Duck的子類都要稍微修改一下飛行的行爲,你又怎麼說?!

 

並非“所有”的子類都具有飛行和呱呱叫的行爲,所以繼承並不是適當的解決方式。

雖然Flyable與Quackable可以解決“一部分”問題(不會再有會飛的橡皮鴨),但是卻造成代碼無法複用,這隻能算是從一個惡夢跳進另一個惡夢。甚至,在會飛的鴨子中,飛行的動作可能還有多種變化……

 

現在我們知道使用繼承並不能很好地解決問題,因爲鴨子的行爲在子類不斷改變,並且所有的子類都有這些行爲是不恰當的

 

Flyable與Quackable接口一開始似乎還挺不錯,解決了問題(只有會飛的鴨子才繼承Flyable),但是Java接口不具有實現代碼,所以繼承接口無法達到代碼的複用。

無論何時你需要修改某個行爲,你必須得往下追蹤並在每一個定義此行爲的類中修改它,一不小心,可能會造成新的錯誤!

 

設計原則

找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。

找到變化並封裝之,以便以後可以輕易地改動或擴充此部分,而不影響不需要變化的其他部分

如果每次新的需求一來,都會使某方面的代碼發生變化,那麼你就可以確定,這部分的代碼需要被抽出來,和其他穩定的代碼有所區分。   

這樣的概念很簡單,幾乎是每個設計模式背後的精神所在。所有的模式都提供了一套方法讓“系統中的某部分改變不會影響其他部分”。


分開變化和不會變化的部分:

除了fly()和quack()的問題之外,Duck類還算一切正常,似乎沒有特別需要經常變化或修改的地方。

爲了要分開“變化和不會變化的部分”,我們準備建立兩組類(完全遠離Duck類),一個是“fly”相關的,一個是“quack”相關的,每一組類將實現各自的動作。

比方說,我們可能有一個類實現“呱呱叫”,另一個類實現“吱吱叫”,還有一個類實現“安靜”。


我們知道Duck類內的fly()和quack()會隨着鴨子的不同而改變。

 

設計鴨子的行爲 :

我們希望一切能有彈性,我們還想能夠“指定”行爲到鴨子的實例。比方說,我們想要產生一個新的綠頭鴨(Mallard)實例,並指定特定“類型”的飛行行爲給它。讓鴨子的行爲可以動態地改變。

換句話說,我們應該在鴨子類中包含設定行爲的方法(SetBehavior),這樣就可以在“運行時動態地改變”綠頭鴨的飛行行爲。

 

針對接口編程,而不是針對實現編程。

 

利用接口代表每個行爲,比方說,FlyBehaviorQuack-Behavior,而行爲的每個實現都將實現其中的一個接口。

 

鴨子類不會負責實現FlyingQuacking接口,反而是由我們製造一組其他類專門實現FlyBehaviorQuackBehavior,這就稱爲“行爲”類。

鴨子的子類將使用接口(FlyBehavior與QuackBehavior)所表示的行爲,所以實際的“實現”不會被綁死在鴨子的子類中。


虛線三角形   -->      實現接口(    △-------------)

 

爲 什 麼非要 把FlyBehavior設計成接口。爲何不使用抽象超類,這樣不就可以使用多態了嗎?

“接口 ” 有多個含義,接口是一個“ 概念”,也是一種Java的interface構造。你可以在不涉及Java interface的情況下,“針對接口編程”,關鍵就在多態。利用多態,程序可以針對超類型編程,執行時會根據實際狀況執行到真正的行爲,不會被綁死在超類型的行爲上。“針對超類型編程”這句話,可以更明確地說成“變量的聲明類型應該是超類型,通常是一個抽象類或者是一個接口,如此,只要是具體實現此超類型的類所產生的對象,都可以指定給這個變量。這也意味着,聲明類時不用理會以後執行時的真正對象類型!

 (C++ 中 抽象類,接口。。。額。。。。不懂)

看看下面這個簡單的多態例子:假設有一個抽象類Animal,有兩個具體的實現(Dog與Cat)繼承Animal。

做法如下:

 

“針對實現編程”

Dog d = new Dog( );

d.bark( );

 

“針對接口/超類型編程”做法會如下:

Animal animal = new Dog( );

animal.makeSound( ); 

 

更棒的是,子類實例化的動作不再需要在代碼中硬編碼,例如newDog(),而是“在運行時才指定具體實現的對象”。

a = getAnimal( );

a.makeSound( );


 

 


這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象複用,因爲這些行爲已經與鴨子類無關了。

而我們可以新增一些行爲,不會影響到既有的行爲類,也不會影響“使用”到飛行行爲的鴨子類。

 

通常在你設計系統時,預先考慮到有哪些地方未來可能需要變化,於是提前在代碼中加入這些彈性。你會發現,原則與模式可以應用在軟件開發生命週期的任何階段。

 

OO系統中,是的,類代表的東西一般都是既有狀態(實例變量)又有方法。

如果你要加上一個火箭動力的飛行動作到SimUDuck系統中,你該怎麼做?

建立一個FlyRocketPowered 類,實現FlyBehavior 接口。

 

整合鴨子的行爲:

class Duck{
protected:
	FlyBehavior *myFlyBehavoir;
public:
	void PerformFly()
	{
		myFlyBehavoir->Fly();		
	};

};
想進行飛的動作,只要叫FlyBehavior對象去飛就好了: myFlyBehavior->Fly();在這部分的代碼中,我們不在乎myFlyBehavior接口的對象到底是什麼,我們只關心該對象知道如何進行飛就夠了。

同理: 想進行呱呱叫的動作,Duck對象只要叫quackBehavior對象去呱呱叫就可以了。

綠頭鴨:

class MallardDuck : public Duck
{
public:
	MallardDuck(){
		myFlyBehavoir = new FlyWithWings();
	}
};

 

上面的FlyWithWings是FlyBehavior的具體實現類;myFlyBehavoir = new FlyWithWings();  MallardDuck就知道怎麼叫怎麼飛了。

 

通過實例化類似Quack或FlyWithWings的行爲類,並把它指定到行爲引用變量中。這種初始化實例變量的做法不夠彈性。

但是因爲quackBehavior的實例變量是一個接口類型,我們能夠在運行時,通過多態的魔力動態地給它地指定不同的QuickBehavior實現類。

#include <iostream>

using namespace std;

class FlyBehavior
{
public:
	virtual void Fly() {};
};

class FlyWithWings :public FlyBehavior
{
	void Fly()
	{
		cout << "fly with wings...\n";
	}
};

class FlyNoWay :public FlyBehavior
{
	void Fly()
	{
		cout << "cannot fly...\n";
	}

};

class Duck{
protected:
	FlyBehavior *myFlyBehavoir;
public:
	void PerformFly()
	{
		myFlyBehavoir->Fly();		
	};
	void setFlyBehavior(FlyBehavior * flyBehavior)
	{
		myFlyBehavoir = flyBehavior;
	}
};

class MallardDuck : public Duck
{
public:
	MallardDuck(){
		myFlyBehavoir = new FlyWithWings();
	}
};

int main()
{	
	Duck *duck = new MallardDuck();
	duck->PerformFly();

	duck->setFlyBehavior(new FlyNoWay());
	duck->PerformFly();

	return 0;
}
 

鴨子的行爲不是繼承來的,而是和適當的行爲對象“組合”來的。

 

多用組合,少用繼承。

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

Motivation

There are common situations when classes differ only in their behavior. For this cases is a good idea to isolate the algorithms in separate classes in order to have the ability to select different algorithms at runtime.

Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.



Strategy - defines an interface common to all supported algorithms. Context uses this interface to call the algorithm defined by a ConcreteStrategy.

Context包含一個抽象類Stategy,該抽象類有一個抽象方法,指定如何調用算法。每個派生類按需要實現該算法。具體實現的職責由Client對象承擔,並轉讓給Context對象

ConcreteStrategy - each concrete strategy implements an algorithm.

Hot points

The strategy design pattern splits the behavior (there are many behaviors) of a class from the class itself. This has some advantages, but the main draw back is that a client must understand how the Strategies differ. Since clients get exposed to implementation issues the strategy design pattern should be used only when the variation in behavior is relevant to them.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章