剛剛開始學習設計模式,以前也接觸過一些,可是歷來都沒有系統的學過,此次打算好好的學習一下。這裏就當是對學習過程的一個記錄、整理,以即可以在之後不時的溫故知新。算法
這一節採用一個鴨子的示例,層層推動,引入策略模式。具體以下:編程
鴨子擁有以下的一些特性:游泳戲水、呱呱叫、外觀設計模式
初步實現鴨子的特性:ide
鴨子超類:學習
public abstract class Duck { public void Quack() { Console.WriteLine("鴨子叫:呱呱呱"); } public void Swim() { Console.WriteLine("鴨子游泳"); } public abstract voidDisplay(); }
鴨子子類:測試
public class MallardDuck:Duck { public override voidDisplay() { Console.WriteLine("綠頭鴨"); } } public class RedHeadDuck : Duck { public override voidDisplay() { Console.WriteLine("紅頭鴨"); } }
以下:設計
public abstract class Duck { public void Quack() { Console.WriteLine("鴨子叫:呱呱呱"); } public void Swim() { Console.WriteLine("鴨子游泳"); } public void Fly() { Console.WriteLine("鴨子飛了"); } public abstract voidDisplay(); }
這個時候會帶來一下新的問題:orm
並不是全部的鴨子都能飛,好比:橡皮鴨對象
並不是全部的鴨子都能呱呱叫:好比:橡皮鴨blog
對應的解決辦法:
經過在橡皮鴨的實現中覆蓋鴨子超類中的Fly和Quack方法
以下:
public class RubberDuck : Duck { public new void Fly() { //Do nothing } public new void Quack() { Console.WriteLine("鴨子叫:吱吱吱"); } }
這樣帶來的新的問題:
好比木頭鴨,既不會游泳 也不會叫
若是直接繼承自Duck超類,還須要對Fly和Quack方法進行重寫。
這個時候咱們可能意識到繼承不是解決問題的最終辦法,由於每個繼承自鴨子的子類都須要去被迫的檢查而且可能要覆蓋Fly和Quack方法。
咱們可能只須要讓某些鴨子具備Fly和Quack便可
經過接口來解決此問題
以下所示:
public interface IFlyable { public void Fly(); } public interface IQuackable { public void Quack(); } public class MoodDuck : IFlyable, IQuackable { public void Fly() { //Can't Fly } public void Quack() { //Can't Quack } }
經過接口的方式雖然結局了一部分鴨子不會飛或者不會叫的問題,
使用接口產生新的問題:
代碼不能複用,產生過多的重複代碼。若是你想要修改某個行爲,必須在買個定義了此行爲的類中修改它,這樣極可能形成新的錯誤。
這個時候出現了第一個設計原則:
找出應用中可能須要變化之處,把它們獨立出來,不要和那些不須要變化的代碼放在一塊兒。
即:
把會變化的部分抽取並封裝起來,以便之後能夠輕易的改動或者擴充此部分,而不影響其餘不變的部分。
分開變化和不會變化的部分,在本例中Fly和Quack會隨着鴨子的不一樣而改變,因此咱們要將其抽離出Dock類,並創建一組新類來表明每一個行爲。
這時出現了設計的另外一個原則:
針對接口編程,而不是針對實現編程。
這裏咱們用接口表明每一個行爲,例如:IFlyable和IQuackable,每一個行爲的實現都將實現對應的接口。
因此鴨子類不會負責實現IFlyable和IQuackable接口,而是由一組其餘專門的類來實現對應的接口,稱之爲「實現類」。
這種作法跟以前作法的區別:
l 以前的作法中行爲的實現來自超類的具體實現,或者繼承某個接口的子類實現,這些作法都依賴於實現。
l 新的設計中,鴨子子類經過使用接口表示行爲,因此實現不會被綁定在子類中,特定的行爲編寫在實現了接口的類中。
「針對接口編程」真正的意思是「針對超類型」編程
即
變量的聲明類型應該是超類型,一般是一個抽象類或者接口,所以只要是實現了抽象類或者接口的對象,均可以指定給這個變量,即聲明類型時不用理會之後執行時真正的對象類型。
接下來咱們就能夠實現鴨子具體的行爲了,以下:
/// 會飛的行爲類 /// </summary> public classFlyWithWings:IFlyable { public void Fly () { Console.WriteLine("用翅膀飛"); } } /// <summary> /// 不會飛的行爲類 /// </summary> public class FlyNoWay : IFlyable { public void Fly() { Console.WriteLine("不會飛"); } } /// <summary> /// 呱呱叫的行爲類 /// </summary> public class Quack : IQuackable { public void Quack() { Console.WriteLine("呱呱叫"); } } /// <summary> /// 吱吱叫的行爲類 /// </summary> public class Squeak : IQuackable { public void Quack() { Console.WriteLine("吱吱叫"); } } /// <summary> /// 不會叫的行爲類 /// </summary> public class MuteQuack :IQuackable { public void Quack() { Console.WriteLine("不會叫"); } }
接下來咱們要對鴨子的行爲進行整合,其核心思想就是:將鴨子飛行和叫的行爲委託別人處理,不使用定義在Duck類內的叫和飛行方法。
具體作法以下:
1. 在Duck類中新增2個實例變量,分別爲「flyBehavior」和「quackBehavior」聲明爲接口類型(每一個鴨子對象會動態的設置這些變量,在運行時引用正確的行爲類型)
2. 將Duck類中的Quack()和Fly()方法刪除,同時新增PerformFly()和PerformQuack()來取代這兩個類
3. 實現PerformFly()和PerformQuack(),以下所示:
/// Description:鴨子超類 /// </summary> public abstract class Duck { public IFlyable flyBehavior; public IQuackablequackBehavior; public void PerformFly() { flyBehavior.Fly(); } public void PerformQuack() { quackBehavior.Quack(); } public void Swim() { Console.WriteLine("鴨子游泳"); } public abstract voidDisplay(); }
這樣作的結果就是咱們能夠忽略flyBehavior和quackBehavior接口對象究竟是什麼,只須要關心該對象如何進行相應的行爲便可。
4. 設定flyBehavior類和quackBehavior類的實例變量,以下所示:
public class MallardDuck : Duck { public MallardDuck() { flyBehavior = newFlyWithWings();//使用FlyWithWings類處理飛行,當PerformFly()被調用時,飛的職責被委託給FlyWithWings對象,獲得真正的飛 quackBehavior = newQuack(); } public override voidDisplay() { Console.WriteLine("綠頭鴨"); } }
說明:
當MallardDuck實例化的時候,構造器會把繼承自quackBehavior的實例變量初始化成Quack類型的新實例,一樣對於飛的行爲也是如此。
5. 測試,以下:
Duck.Duck mallard = new MallardDuck(); mallard.PerformFly(); mallard.PerformQuack(); Console.Read();
結果以下:
動態的設定行爲
爲了可以充分的用到咱們以前建的一些鴨子的動態行爲,咱們能夠在鴨子子類中經過設定方法來設定鴨子的行爲,而不是在樣子的構造器內實例化。具體步驟以下:
1. 在Duck類中新增兩個設置方法,以下:
public void SetFlyBehavior(IFlyable fly) { flyBehavior = fly; } public voidSetQuackBehavior(IQuackable quack) { quackBehavior = quack; }
2. 建立新的鴨子類型
public class ModelDuck : Duck { public ModelDuck() { flyBehavior = newFlyNoWay(); quackBehavior = newSqueak(); } public override void Display() { Console.WriteLine("模型鴨"); } }
3. 建立一個新的FlyBehavior類型
public class FlyRocket : IFlyable { public void Fly() { Console.WriteLine("Fly with Rocket!"); } }
4. 測試
Duck.Duck model = new ModelDuck(); model.PerformFly(); model.PerformQuack(); model.SetFlyBehavior(newFlyRocket()); model.PerformFly();
結果以下:
從上邊的結果中咱們能夠看出,在初始化ModelDuck的時候,咱們對flyBehavior對象進行了FlyNoWay的初始化,因此顯示的飛行的行爲爲:不會飛,後邊咱們又經過SetFlyBehavior方法動態設置了flyBehavior的實例,因此後邊就有了Fly with Rocket的行爲啦。
上邊的例子對於每個鴨子都有一個FlyBehavior和QuackBehavior,當你將兩個類結合起來使用,就是組合,這種作法和繼承的不一樣之處在於,鴨子的行爲不是繼承來的,而是合適的對象組合來的。這裏用到了面向對象的第三個設計原則:
多用組合,少用繼承
經過剛剛講的這麼多,引出了本章的第一個模式:
策略模式:定義了算法簇(這裏的算法簇就至關於一組行爲),分別封裝起來,讓他們之間能夠相互替換,此模式讓算法的變化獨立於使用算法的客戶。
經過本章的學習,咱們能夠了解掌握如下知識:
1. 面向對象設計的原則(部分):
l 封裝變化
l 多用組合,少用繼承
l 針對接口編程,不針對實現編程
2. 面向對象模式---策略模式:
定義算法簇,分別封裝起來,讓它們之間能夠相互替換,此模式讓算法變化獨立於使用算法的客戶。