設計模式學習筆記之策略模式

birdlove1987發表於2017-02-12

策略模式定義了一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化。(來自百度的定義)


單從概念上看總覺得暈乎乎的,下面我們來看一個小例子:


下面是一個模擬小鴨子的列子:




首先,按照OOP的思想先構造一個鴨子的基類:


public abstract class Duck {
	//建構函式
	public Duck() {
	}
	//鴨子叫方法
	public void Quack() {
		System.out.println("嘎嘎叫");
	}
	//鴨子游泳方法
	public void swim() {
		System.out.println("游泳啦");
	}
	//展示的方法寫成抽象方法由子類實現
	public abstract void display();
	
}


現在讓我們構造兩個具體的鴨子:白鴨子和黑鴨子(ps:差點打成周黑鴨。。。原諒一個吃貨吧。。)

白鴨子:


public class WhiteDuck extends Duck {

	@Override  //白鴨子自己的表現函式
	public void display() {
		System.out.println("我是白鴨子");
	}

}


黑鴨子:


public class BlackDuck extends Duck {

	@Override  //黑鴨子自己的表現函式
	public void display() {
		System.out.println("我是黑鴨子");
	}

}


我們寫一個主方法來測試一下:


public class StimulateDuck {
	public static void main(String[] args) {
		
		BlackDuck mBlackDuck = new BlackDuck();
		WhiteDuck mWhiteDuck = new WhiteDuck();

		mBlackDuck.display();
		mBlackDuck.Quack();
		mBlackDuck.swim();

		mWhiteDuck.display();
		mWhiteDuck.Quack();
		mWhiteDuck.swim();
	}
}

測試結果:




現在我們想要擴充套件鴨子的功能比如:飛。

我們首先想到的方法將飛這個方法寫到鴨子的基類當中:


public abstract class Duck {
	//建構函式
	public Duck() {
	}
	//鴨子叫的方法
	public void Quack() {
		System.out.println("嘎嘎叫");
	}
	//鴨子游泳的方法
	public void swim() {
		System.out.println("游泳啦");
	}
	//鴨子飛的方法
	public void Fly() {
			System.out.println("我會飛");
		}
	//展示的方法寫成抽象方法由子類實現
	public abstract void display();
	
	
}


但是這樣做會有一個問題,就是並不是所有的鴨子都會飛,這樣設計會產生很嚴重的bug。

然後我們會想到可以讓不同的鴨子的子類覆蓋飛的方法,但是如果鴨子的種類很多,這將是一個非常大的工作量。

ps:繼承的問題:對類的區域性改動,尤其超類的區域性改動,會影響其他部分。影響會有溢位效應。超類挖的一個坑,每個子類都要來填,增加工作量,複雜度0(N^2)。


面對這樣的問題我們便可以引入策略模式來處理。

首先我們要先對問題進行分析,分析專案中變化與不變的部分,提取變化的部分,使用抽象介面來實現。

我們回到我們自己的鴨子專案當中,仔細想一想鴨子的叫聲,游泳和飛行對於不同的鴨子來說都是會變化的。所以我們可以將這三個部分抽象成介面:


//叫聲介面
public interface QuackBehavior
{
	void quack();
}
//游泳介面
public interface SwimBehavior
{
	void swim();
}
//飛行介面
public interface FlyBehavior
{
	void fly();
}


通過這些介面我們可以進一步構建行為族,用飛行方式來舉例:


//飛行介面
public interface FlyBehavior
{
	void fly();
}

//擅長飛行類
public class GoodFlyBehavior implements FlyBehavior
{
	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("飛的很好");
	}
}

//不擅長飛行類
public class BadFlyBehavior implements FlyBehavior
{
	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("飛的不好");
	}
}

//不會飛行類
public class NoFlyBehavior implements FlyBehavior
{
	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("不會飛");
	}
}


這樣我們就可以在基類裡定義這些行為的引用,並通過呼叫具體行為的函式來實現行為,在子類中來具體實現這些行為引用。


public abstract class Duck {
	
	//行為引用
	FlyBehavior mFlyBehavior;
	QuackBehavior mQuackBehavior;
	SwimBehavior mSwimBehavior;
	
	//建構函式
	public Duck() {

	}
	
	//飛行方法
	public void Fly() {
		mFlyBehavior.fly();
	}
	
	//叫聲方法
	public void Quack() {
		mQuackBehavior.quack();
	}
	
	//游泳方法
	public void swim() {
		mSwimBehavior.swim();
	}
	
	//展示的方法寫成抽象方法由子類實現
	public abstract void display();

}


這事我們再來重新實現子類

白鴨子:


public class WhiteDuck extends Duck {

	public WhiteDuck() {
		//子類中實現具體的行為方式
		mFlyBehavior = new GoodFlyBehavior();
		mQuackBehavior = new GaGaQuackBehavior();
		mSwimBehavior = new  GoodSwimBehavior();
	}

	@Override
	public void display() {
		System.out.println("我是白鴨子");
	}
}

黑鴨子:


public class BlackDuck extends Duck {

	public BlackDuck() {
		//子類中實現具體的行為方式
		mFlyBehavior = new BadFlyBehavior()
		mQuackBehavior = new GeGeQuackBehavior();
		mSwimBehavior = new  BadSwimBehavior();
	}

	@Override
	public void display() {
		System.out.println("我是黑鴨子");
	}
}


這樣設計的好處:新增行為簡單,行為類更好的複用,組合更方便。既有繼承帶來的複用好處,沒有挖坑。

下面我們再來重新認識一下策略模式


策略模式:分別封裝行為介面,實現演算法族,超類裡放行為介面物件,在子類裡具體設定行為物件

原則:分離變化部分,封裝介面,基於介面程式設計各種功能。這種模式讓行為演算法的變化獨立與演算法的使用者。

我們現在寫一個測試類測試一下我們重新設計的鴨子模型:


public class StimulateDuck {

	public static void main(String[] args) {
		//多型特性
		Duck mWhiteDuck = new WhiteDuck();
		Duck mBlackDuck = new BlackDuck();

		mWhiteDuck.display();
		mWhiteDuck.Fly();
		mWhiteDuck.Quack();
		mWhiteDuck.swim();

		mBlackDuck.display();
		mBlackDuck.Fly();
		mBlackDuck.Quack();
		mBlackDuck.swim();
	}
}


輸出結果



這樣設計確實方便了以後的擴充套件啊~~~~~

而且如果我們也可以很方便的動態的重新改變鴨子行動特性


public abstract class Duck {
	
	//行為引用
	FlyBehavior mFlyBehavior;
	QuackBehavior mQuackBehavior;
	SwimBehavior mSwimBehavior;
	
	//建構函式
	public Duck() {

	}
	
	//飛行方法
	public void Fly() {
		mFlyBehavior.fly();
	}
	
	//叫聲方法
	public void Quack() {
		mQuackBehavior.quack();
	}
	
	//游泳方法
	public void swim() {
		mSwimBehavior.swim();
	}
	
	//動態改變叫聲行為
	public void SetQuackBehavoir(QuackBehavior qb) {
		mQuackBehavior = qb;
	}
	
	//動態改變飛行行為
	public void SetFlyBehavoir(FlyBehavior fb) {
		mFlyBehavior = fb;
	}
	
	//動態改變游泳行為
	public void SetSwimBehavior(SwimBehavior sb) {
		mSwimBehavior = sb;
	}
	
	//展示的方法寫成抽象方法由子類實現
	public abstract void display();
}


我們在測試函式中改變黑鴨子的飛行和叫聲試一下


public class StimulateDuck {

	public static void main(String[] args) {

		Duck mWhiteDuck = new WhiteDuck();
		Duck mBlackDuck = new BlackDuck();

		mWhiteDuck.display();
		mWhiteDuck.Fly();
		mWhiteDuck.Quack();
		mWhiteDuck.swim();

		mBlackDuck.display();
		mBlackDuck.Fly();
		mBlackDuck.Quack();
		mBlackDuck.swim();
		
		//動態改變飛行行為
		mBlackDuck.SetFlyBehavoir(new NoFlyBehavior());
		mBlackDuck.Fly();
		
		//動態改變叫聲行為
		mBlackDuck.SetQuackBehavoir(new NoQuackBehavior());
		mBlackDuck.Quack();
	}
}

測試結果




感覺方便多了!!


最後我們來總結一下策略模式的優缺點:


優點:
1、 策略模式提供了管理相關的演算法族的辦法。策略類的等級結構定義了一個演算法或行為族。恰當使用繼承可以把公共的程式碼轉移到父類裡面,從而避免重複的程式碼。


2、 策略模式提供了可以替換繼承關係的辦法。繼承可以處理多種演算法或行為。如果不是用策略模式,那麼使用演算法或行為的環境類就可能會有一些子類,每一個子類提供一個不同的演算法或行為。但是,這樣一來演算法或行為的使用者就和演算法或行為本身混在一起。決定使用哪一種演算法或採取哪一種行為的邏輯就和演算法或行為的邏輯混合在一起,從而不可能再獨立演化。繼承使得動態改變演算法或行為變得不可能。


3、 使用策略模式可以避免使用多重條件轉移語句。多重轉移語句不易維護,它把採取哪一種演算法或採取哪一種行為的邏輯與演算法或行為的邏輯混合在一起,統統列在一個多重轉移語句裡面,比使用繼承的辦法還要原始和落後。


缺點:
1、客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演算法的區別,以便適時選擇恰當的演算法類。換言之,策略模式只適用於客戶端知道所有的演算法或行為的情況。


2、 策略模式造成很多的策略類,每個具體策略類都會產生一個新類。有時候可以通過把依賴於環境的狀態儲存到客戶端裡面,而將策略類設計成可共享的,這樣策略類例項可以被不同客戶端使用。換言之,可以使用享元模式來減少物件的數量。


相關文章