1. 程式人生 > >Head First設計模式 第一章:策略模式

Head First設計模式 第一章:策略模式

模擬鴨子應用:

繼承:

設計一個遊戲。裡面有各種鴨子。一開始我們建立一個鴨子超類(Suprclass),並讓各種鴨子來繼承這個類。

如:

對所有鴨子通用的方法由超類來實現,對每個鴨子子類不同的方法由各個子類來實現該方法。

這時,如果我們想讓鴨子能飛,我們又在超類裡建立了一個fly()方法。

問題來了,新的子類橡皮鴨子也繼承了這個方法,橡皮鴨子也能飛,這產生了衝突。

如:

如果一個方法只有某些子類具備而有些子類不具備,此時將這個方法放到超類中就會產生一系列問題。

當涉及“維護”時,為了複用目的而使用繼承時,結局並不完美。

介面:

由於fly()方法不是對所有子類都適用,我們考慮將fly()從超類中拿出來,建立一個Flyable介面,只有會飛的鴨子子類才需要實現這個介面。同樣也可以設計一個Quackable介面,因為不是所有的鴨子都會叫。 

如:

由於Java介面不具有實現程式碼,所以繼承介面無法達到程式碼的複用。我們就的給每個會飛的鴨子實現fly方法,程式碼量會很大。

第一個設計原則:

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

或者另一種說法:把會變化的部分取出並封裝起來,以便以後可以輕易地改動或擴充此部分,而不影響不需要變化的其他部分。

針對上面鴨子的例子,我們建立兩組類(完全遠離Duck類), 一個是"fly"相關的, 一個是"quack"相關的,每一組類將實現各自的動作。比方說,我們可能有一個類實現“呱呱叫“,另一個類實現“吱吱叫“,還有一個類實現“安靜”。

如:

第二個設計原則:

針對介面程式設計,而不是針對實現程式設計。

這次鴨子類不會負責實現Flying與Quacking介面,反而是由我們製造一組其他類專門實現FlyBehavior與QuackBehavior, 這
就稱為"行為"類。由行為類而不是Duck類來實現行為介面。

如:

鴨子的FlyBehavior介面下面有一組飛行行為類,每種飛行行為的具體動作不相同。

“ 針對介面程式設計“ 真正的意思是“ 針對超型別( supertype ) 程式設計”。“針對超型別程式設計”這句話,可以更明確地說成“變數的宣告類應該是超型別,通常是一個抽象類或者是一個介面,這樣只要是具體實現此超型別的子類所產生的物件,都可以指定給這個變數。

多型:

上面這段話的意思其實就是多型。即父類的引用變數指向子類物件。

多型的格式:

父類型別 變數名 = new 子類型別();
變數名.方法名();

所使用的方法往往被在子類中被實現(即覆寫了父類的同名方法)。

如:

回到上面的鴨子問題。我們給FlyBehavior和QuackBehavior介面寫一組實現類。

如:

這樣一來,我們可以讓動作被其他物件複用,還可以新增一些行為,既不會影響到已有的行為類,也不會影響到“使用”飛行行為的鴨子類。

關鍵在於, 鴨子現在會將飛行和呱呱叫的動作“ 委託" ( delegate ) 別人處理, 而不是使用定義在Duck類( 或子類) 內的呱呱叫和飛行方法。

具體做法:

首先,在Duck類中“加入兩個例項變數”,分別為"flyBehavior" 與"quackBehavior" , 宣告為介面型別(而不是具體類實現型別),每個鴨子物件都會動態地設定這些變數以在執行時引用正確的行為型別(例如: FlyWithWings 、Squeak等)。

我們也必須將Duck類與其所有子類中的fty()與quack()刪除,因為這些行為已經被搬到FlyBehavior與QuackBehavior類中了。

我們用兩個相似的方法performFly()和perform Quack()取代Duck類中的fly()與quack()。

如:

現在,我們來實現performQuack():

如何設定flyBehavior與quackBehavior的例項變數:

我們現在來看看其中一個子類:綠頭鴨

當MallardDuck例項化時,它的構造器會把繼承來的quackBehavior例項變數初始化成Quack型別的新例項(Quack是QuackBehavior的具體實現類)。

MallardDuck的構造器也會將flyBehavior例項變晝初始化成FlyWithWings型別的例項(FlyWith Wings是Fly Behavior的具休實現類)。

下面我們來看一下完整的實現:

1、輸入並編譯下面的Duck類( Duck.java ) 以及兩頁前的MallardDuck類( MallardDuck.java) 。

2、輸入並編譯FlyBehavior介面( FlyBehavior.Java) 與兩個行為實現類( FlyWithWings .java與FlyNoWay.java) 。

3、輸入並編譯QuackBehavior介面( QuackBehavior.java ) 及其三個實現類( Quack .java 、MuteQuack .java 、Squeak.java ) 。

4、輸入並編譯測試類( MiniDuckSimulator.java )。

程式碼如下:

//Duck.java
public abstract class Duck {
    //父類中宣告兩個介面FlyBehavior和QuackBehavior的例項變數
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;

	public Duck() {
	}

	public abstract void display();

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

	public void performQuack() {
		quackBehavior.quack();
	}

	public void swim() {
		System.out.println("All ducks float, even decoys!");
	}
}
//MallardDuck.java
public class MallardDuck extends Duck {
	public MallardDuck() {
        //子類中將兩個介面的變數例項化成對應的實現行為子類,方法我們事先已經寫好了一組
        //根據你寫的子類的不同,選擇相應的方法
        //如綠頭鴨這個子類叫聲應該是呱呱叫即Quack行為類,飛行應該是FlyWithWings行為類
        //Quack行為類實現QuackBehavior介面,FlyWithWings行為類實現FlyBehavior介面
        //Quack行為類覆寫了QuackBehavior的quack()方法
        //FlyWithWings行為類覆寫了FlyBehavior介面的fly()方法
        //這樣在建立一個多型的變數時如果new一個MallardDuck
        //quackBehavior變數和flyBehavior變數會例項化成對應的實現行為子類
        //再呼叫quack()和fly()方法時呼叫的是實現行為子類中覆寫後的方法
        //這樣就實現了不同的鴨子的同一類行為的靈活配置
		quackBehavior = new Quack();
		flyBehavior = new FlyWithWings();
	}

	public void display() {
		System.out.println("I'm a real Mallard duck");
	}
}
//FlyBehavior.Java
public interface FlyBehavior {
	public void fly();
}
//FlyWithWings.java
public class FlyWithWings implements FlyBehavior {
	public void fly() {
		System.out.println("I'm flying!!");
	}
}
//FlyNoWay.java
public class FlyNoWay implements FlyBehavior {
	public void fly() {
		System.out.println("I can't fly");
	}
}
//QuackBehavior.java
public interface QuackBehavior {
	public void quack();
}
//Quack.java
public class Quack implements QuackBehavior {
	public void quack() {
		System.out.println("Quack");
	}
}
//MuteQuack.java
public class MuteQuack implements QuackBehavior {
	public void quack() {
		System.out.println("<< Silence>>");
	}
}
//Squeak.java
public class Squeak implements QuackBehavior {
	public void quack() {
		System.out.println("Squeak");
	}
}
//MiniDuckSimulator.java,最後執行這個.java檔案
public class MiniDuckSimulator {
	public static void main(String[] args) {
        //多型的定義格式:就是父類的引用變數指向子類物件
        //即:父類型別 變數名 = new 子類型別();
        //變數名.方法名();
		Duck mallard = new MallardDuck();
        //performQuack()方法和performFly()方法都繼承自Duck類
        //這兩個方法分別呼叫quackBehavior變數的quack()方法、flyBehavior變數的fly()方法
        //由於在前面我們已經在MallardDuck子類中將quackBehavior變數和flyBehavior變數例項化成對應的實現行為子類Quack和FlyWithWings,所以實際呼叫的quack()方法和fly()方法是實現行為子類中覆寫的quack()方法和fly()方法
		mallard.performQuack();
		mallard.performFly();
	}
}

執行截圖如下:

動態設定行為:

假設我們想在鴨子子類中通過“設定方法(setter method) "來設定鴨子的行為,而不是在鴨子的構造器內例項化。

1、在Duck類中,加入兩個新方法:

2、製造一個新的鴨子型別:模型鴨(ModelDuck.java)

3、建立一個新的FlyBehavior行為型別(FlyRocketPowered .java)

4、改變測試類( MiniDuckSimulator.java ) , 加上模型鴨,井使模型鴨具有火箭動力。

在執行時如果想改變鴨子的行為,只需要呼叫鴨子的setter()方法即可。

程式碼如下:

//Duck.java
public abstract class Duck {
    //父類中宣告兩個介面FlyBehavior和QuackBehavior的例項變數
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;

	public Duck() {
	}

	public abstract void display();

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

	public void performQuack() {
		quackBehavior.quack();
	}

    //兩個用於改變鴨子行為的方法
	public void setFlyBehavior(FlyBehavior fb) {
		flyBehavior = fb;
	}

	public void setQuackBehavior(QuackBehavior qb) {
		quackBehavior = qb;
	}

	public void swim() {
		System.out.println("All ducks float, even decoys!");
	}
}
//MallardDuck.java
public class MallardDuck extends Duck {
	public MallardDuck() {
        //子類中將兩個介面的變數例項化成對應的實現行為子類,方法我們事先已經寫好了一組
        //根據你寫的子類的不同,選擇相應的方法
        //如綠頭鴨這個子類叫聲應該是呱呱叫即Quack行為類,飛行應該是FlyWithWings行為類
        //Quack行為類實現QuackBehavior介面,FlyWithWings行為類實現FlyBehavior介面
        //Quack行為類覆寫了QuackBehavior的quack()方法
        //FlyWithWings行為類覆寫了FlyBehavior介面的fly()方法
        //這樣在建立一個多型的變數時如果new一個MallardDuck
        //quackBehavior變數和flyBehavior變數會例項化成對應的實現行為子類
        //再呼叫quack()和fly()方法時呼叫的是實現行為子類中覆寫後的方法
        //這樣就實現了不同的鴨子的同一類行為的靈活配置
		quackBehavior = new Quack();
		flyBehavior = new FlyWithWings();
	}

	public void display() {
		System.out.println("I'm a real Mallard duck");
	}
}
//FlyBehavior.Java
public interface FlyBehavior {
	public void fly();
}
//FlyWithWings.java
public class FlyWithWings implements FlyBehavior {
	public void fly() {
		System.out.println("I'm flying!!");
	}
}
//FlyNoWay.java
public class FlyNoWay implements FlyBehavior {
	public void fly() {
		System.out.println("I can't fly");
	}
}
//FlyRocketPowered.java
public class FlyRocketPowered implements FlyBehavior {
	public void fly() {
		System.out.println("I'm flying with a rocket!");
	}
}
//QuackBehavior.java
public interface QuackBehavior {
	public void quack();
}
//Quack.java
public class Quack implements QuackBehavior {
	public void quack() {
		System.out.println("Quack");
	}
}
//MuteQuack.java
public class MuteQuack implements QuackBehavior {
	public void quack() {
		System.out.println("<< Silence>>");
	}
}
//Squeak.java
public class Squeak implements QuackBehavior {
	public void quack() {
		System.out.println("Squeak");
	}
}
//ModelDuck.java
public class ModelDuck extends Duck {
	public ModelDuck() {
		flyBehavior = new FlyNoWay();
		quackBehavior = new Quack();
	}

	public void display() {
		System.out.println("I'm a model duck");
	}
}
//MiniDuckSimulator.java,最後執行這個.java檔案
public class MiniDuckSimulator {
	public static void main(String[] args) {
        //多型的定義格式:就是父類的引用變數指向子類物件
        //即:父類型別 變數名 = new 子類型別();
        //變數名.方法名();
		Duck mallard = new MallardDuck();
        //performQuack()方法和performFly()方法都繼承自Duck類
        //這兩個方法分別呼叫quackBehavior變數的quack()方法、flyBehavior變數的fly()方法
        //由於在前面我們已經在MallardDuck子類中將quackBehavior變數和flyBehavior變數例項化成對應的實現行為子類Quack和FlyWithWings,所以實際呼叫的quack()方法和fly()方法是實現行為子類中覆寫的quack()方法和fly()方法
		mallard.performQuack();
		mallard.performFly();
        //建立一個新的子類表示模型鴨
        Duck model = new ModelDuck();
        //先看看模型鴨原本的fly方法
		model.performFly();
        //修改模型鴨的fly方法為FlyRocketPowered()方法,new一個FlyRocketPowered行為類重新例項化flyBehavior變數
		model.setFlyBehavior(new FlyRocketPowered());
        //看看模型鴨的fly方法是否更新成功
		model.performFly();
	}
}

執行截圖如下:

重新設計後的類結構:鴨子子類繼承Duck ,飛行行為實現FlyBehavior介面,呱呱叫行為實現QuackBehavior介面。每一鴨子都有一個FlyBehavior和一個QuackBehavior, 好將飛行和呱呱叫委託給它們代為處理。當你將兩個類結合起來使用,如同本例一般,這就是組合(composition) 。

請特別注意類之間的“關係”。關係可以是IS-A (是一個)、HAS-A(有一個)或IMPLEMENTS (實現)。

第三個設計原則:

多用組合,少用繼承。使用組合建立系統具有很大的彈性,不僅可將演算法族封裝成類,更可以“在執行時動態地改變行為" , 只要
組合的行為物件符合正確的介面標準即可。

策略模式:

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