策略模式學習2

接着我的上一篇文章策略模式學習1。

讓我們來回顧一下上面設計中橡皮鴨類的代碼,並提出一下問題。

/**
 * 橡皮鴨
 * @author Administrator
 *
 */
public class RubberDuck extends Duck {
	
	@Override
	void display() {
		System.out.println("橡皮鴨...");
	}
	
	/*
	 * 橡皮鴨子不會呱呱叫
	 * 但是問題來了,這個橡皮鴨子會飛?
	 * 那設計人員會說:一樣覆蓋掉就好
	 */
	@Override
	public void quack() {
		//覆蓋超類的呱呱叫
		System.out.println("吱吱叫...");
	}
	
	@Override
	void fly() {
		//覆蓋,變成什麼事兒都不做
		;
	}
	
	/*
	 * 那麼問題來了,以後每個月更新,常常會出現新的鴨子子類,這要被迫每次檢查
	 * 並可能覆蓋quack() 和 fly() 這簡直就是無窮無盡的噩夢!!!!
	 * 這樣設計的缺點:
	 * 1.代碼在多個子類中重複
	 * 2.運行時的行爲不容易被改變
	 * 3.改變會牽一髮動全身,造成其他鴨子不想要的改變
	 * 4.很難知道鴨子的全部行爲,因爲有了不屬於它本身的行爲。
	 */
	
	//重複代碼變多,那麼你認爲覆蓋幾個方法就算是差勁,那麼對於48個Duck子類都要稍微修改一下
	//飛行的行爲,你又怎麼說?需求:會飛的鴨子中,飛的動作可能有多種變化.
	

}

因此我們可以看到,利用繼承來提供Duck的行爲,會導致以上所述的問題以及缺點。
那利用接口如何?將飛行方法和呱呱叫的方法做成一個接口,分別讓具備這兩個行爲的鴨子實現這個接口,就能夠解決不再會有會飛的鴨子。
但是缺點是:造成了代碼無法複用(雖然鴨子類中實現了飛行的方法,
但是這個鴨子在某個場景下又想要其他類型的飛行方法呢?這有點類似於遊戲中的升級,解鎖了某技能或在某個副本中,會以新的行爲出現)。這又跳進了另外一個坑。

另外,如果飛行的動作可能還有多種變化呢?比如火箭式飛行,螺旋式飛行等等。

軟件開發的一個不變的真理:改變(change).不管軟件設計得多好,一段時間之後,總是需要成長和改變,否則軟件就會“死亡”。

利用Flyable和Quackable一開始似乎還不錯,解決了只有會飛的鴨子才實現Flyable,但是java接口不具有實現代碼,所以實現接口無法達到代碼的複用。這就意味着無論何時你需要修改某個行爲,你必須得往下追蹤到並在每一個定義此行爲的類中修改它,一不小心,可能會造成新的錯誤。

幸運的是,這裏有一個設計原則:

找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。換句話說:如果每次新的需求一來,都會使某方面的代碼發生變化,那麼你就可以確定,這部分的代碼需要被抽出來,和其他穩定的代碼有所區分。對這個原則的另外一種思考方式:把會變化的部分取出並封裝出來,以便以後可以輕易地改動或擴充此部分,而不影響不需要變化的其他部分。

我們知道Duck類的fly()和quack()會隨着鴨子的不同而改變。因此我們把這兩個行爲從Duck類中分離出來,建立一組新類來代表每個行爲。

如何設計鴨子的行爲?我們想要產生一個新的綠頭鴨實例,並指定特定的“類型”的飛行行爲給它。那麼幹脆順便讓鴨子的行爲可以動態的改變好了。換句話說:我們應該在鴨子類中包含設定行爲的方法,這樣就可以在“運行時”動態地“改變”綠頭鴨的飛行行爲。

設計原則:針對接口編程,而不是針對實現編程。

public abstract class Duck {
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;
	// 行爲變量被聲明爲行爲 "接口" 類型
	public void swim() {
		System.out.println("游來游去...");
	}
	
	abstract void display();
	//鴨子對象不親自處理呱呱叫行爲,而是委託給quackBehavior引用的對象去執行
	void performQuack() {
		quackBehavior.quack();
	}

	void performFly() {
		flyBehavior.fly();
	}

	public FlyBehavior getFlyBehavior() {
		return flyBehavior;
	}

	public void setFlyBehavior(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	public QuackBehavior getQuackBehavior() {
		return quackBehavior;
	}

	public void setQuackBehavior(QuackBehavior quackBehavior) {
		this.quackBehavior = quackBehavior;
	}
	
	/*public void quack() {
		System.out.println("呱呱叫...");
	}
	
	void fly(){
		System.out.println("飛來飛去...");
	}*/
	
	//鴨子的其他方法
}

public interface FlyBehavior {
	void fly();
}

public class FlyWithWings implements FlyBehavior {

	@Override
	public void fly() {
		System.out.println("這裏實現了所有有翅膀的鴨子飛行動作...");
	}

}

public class FlyNoWay implements FlyBehavior {

	@Override
	public void fly() {
		//什麼都不做,不會飛
		System.out.println("I can't fly!");
	}

}

public class FlyByRocky implements FlyBehavior {

	@Override
	public void fly() {
		// 新增了一個火箭式的飛行
		System.out.println("火箭式的飛行...");

	}

}

public interface QuackBehavior {
	void quack();
}

public class Quack implements QuackBehavior {

	@Override
	public void quack() {
		// 實現鴨子呱呱叫
		System.out.println("呱呱叫...");

	}

}

public class Squeak implements QuackBehavior {

	@Override
	public void quack() {
		// 橡皮鴨子吱吱叫
		System.out.println("吱吱叫...");
	}

}

public class MallardDuck extends Duck {

	@Override
	void display() {
		System.out.println("綠頭鴨..");

	}

}

public class ModelDuck extends Duck {
	
	public ModelDuck(){
		this.flyBehavior = new FlyNoWay();
		this.quackBehavior = new MuteQuack();
	}
	@Override
	void display() {
		// 這是一隻模型鴨
		System.out.println("模型鴨...");
	}
	
	

}

// 新增一個鴨鳴器,用來捕獲其他真正鴨子
public class DuckCall implements QuackBehavior{
	QuackBehavior quack;
	
	public QuackBehavior getQuack() {
		return quack;
	}

	public void setQuack(QuackBehavior quack) {
		this.quack = quack;
	}

	@Override
	public void quack() {
		quack.quack();
	}

	

}
//測試類
public class MiniDuckSimulator {

	public static void main(String[] args) {
		/*Duck mallard = new MallardDuck();
		mallard.setFlyBehavior(new FlyWithWings());
		mallard.setQuackBehavior(new Quack());
		mallard.performFly();
		mallard.performQuack();*/
		
		/*Duck modelDuck = new ModelDuck();
		modelDuck.performFly();
		modelDuck.setFlyBehavior(new FlyByRocky());//模型鴨可以動態地改變飛行行爲
		modelDuck.setQuackBehavior(new MuteQuack());
		modelDuck.performFly();*/
		
		//實現一個鴨鳴器用來誘捕野鴨
		DuckCall duckCall = new DuckCall();
		duckCall.setQuack(new Quack());
		duckCall.quack();//利用多態,可以動態的模擬各種叫聲
	}

}

public class MuteQuack implements QuackBehavior {

	@Override
	public void quack() {
		// 什麼都不做,不會叫
		;
	}

	/*
	 * 這樣設計,可以讓飛行和呱呱叫的動作被其他的對象複用,因爲這樣行爲已經與鴨子無關了
	 * 而我們可以新增一些行爲,不會影響到既有的行爲類,不會影響“使用”到飛行行爲的鴨子類了。
	 * 有了繼承的“複用”的好處,卻沒有了繼承所帶來的包袱
	 */
}

這裏也用到另外一個設計原則:多用組合,少用繼承。

“有一個”可能比“是一個”更好。有一個關係相當有趣,每個鴨子都有一個FlyBehavior和QuackBehavior,當你將兩個類結合起來使用,如通本例一般,這就是用到了組合。




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