看老外程序員如何向妻子解釋設計模式

設計模式是什麼?

 Shubho:通過我們關於面向對象設計原則(OODP,即SOLID原則)的對話,我想你已經對面向對象設計原則(OODP)有了基本的認識。希望你不要介意我把對話分享到博客上。你可以在這找到它:<如何向妻子解釋OOD>.

設計模式是這些原則在某些特定公共場景下標準化的應用,接下來讓我們通過一些例子學習什麼是設計模式。

Farhana: 當然,我喜歡例子。

Shubho: 讓我們以汽車爲例討論一下。汽車是一個很複雜的對象,由成千上萬的其它對象組成,如發動機,車輪,方向盤,車座,車體等等其他不同的部分或部件。

汽車部件

  當裝配汽車時,製造商需要集中並裝配這些更小的自成汽車子系統的不同部件。而這些不同的小部件同樣也是複雜的對象,其它製造商同樣要生產並組裝它們。在生產汽車時,汽車公司並不會爲怎麼生產組裝這些部件操心(前提是他們要確保這些對象/設備的質量)。當然,汽車製造商更加關心怎麼裝配這些不同部件以便能生產不同型號的汽車。

通過遵循不同的設計,組裝不同的部件,生產不同型號的汽車

  Farhana: 汽車製造公司必須有如何生產不同型號汽車的設計圖或藍圖,對嗎?

  Shubho: 當然,並且這些設計都是良好的,他們花費大量的時間和精力來做這些設計。一旦設計完成,生產汽車就僅僅是照葫蘆畫瓢了。

  Farhana: 嗯。如果事先有一些好的設計,就能在短時間內遵照這些設計生產不同產品,並且製造商在每次生產某一個型號產品時就不需要重新設計或重新發明車輪,他們只需要按照已有的設計辦事就行了。

生產不同型號產品(汽車)的不同設計圖

  Shubho: 你抓到重點了。現在假設我們是軟件生產商,我們使用基於需求而來的不同組件或功能構建各種不同的軟件程序。當生產這些不同軟件系統時,我們常常需要爲一些不同軟件系統中存在的相同情況開發代碼,對嗎?

  Farhana: 是的,在開發不同軟件程序時經常遇到相同的設計問題。

  Shubho: 我們嘗試使用面向對象的方式開發軟件,並嘗試應用OOPD來讓代碼能易於維護,可複用,可擴展。無論什麼時候,當我們遇到這些設計問題時,如果我們有一組經過謹慎開發,良好測試的對象以供使用會不會更好呢?

  Farhana: 是的,這樣能夠節省時間,生產出更好的軟件,且利於以後維護。

  Shubho: 很好!從設計上來說,它的好處是你不需要開發那些對象。經過多年發展,人們已經遇到過一些類似的設計問題,並已經形成有一些公認的,良好的已標準化的設計方案。我們稱之爲設計模式。

 我們一定好感謝四人組,他們在《設計模式:可複用面向對象軟件設計》中總結出了23種基本的設計模式。四人組由Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides組成。實際中有很多面向對象設計模式,但這23種模式被公認爲是所有其他設計模式的基礎。

  Farhana: 我能發明一個新的模式嗎?這可能嗎?

  Shubho: 當然,親愛的,爲什麼不能呢?!設計模式不是由科學家發明創造的。它們是被發現找到的。這意味着任何通用問題場景中都有一些好的設計方案在那。如果我們能夠指出一個能夠解決一個新的設計相關問題的面向對象設計,那麼這將會是一個由我們定義的新的設計模式。誰知道呢?!如果我們發現找到一些設計模式,或許將來有一天人們會稱我們爲二人組,哈哈。

  Fahana: :)

  我們將如何學習設計模式?

Shubho: 我一直認爲例子是學習的最好途徑。在我們的學習方法中,我們不會先討論理論後討論實現。我認爲這是很糟糕的方式。設計模式不是基於理論的發明。事實上,問題場景首先出現,其次是基於這些問題的來龍去脈和需求,然後是一些設計方案的演化,最後其中的一些被標準化爲模式。所以對每一個我們討論的設計模式,我們將嘗試理解並分析一些現實生活中的例子,然後一步步嘗試歸納一個設計,並最後總結一些與某些模式匹配設計。設計模式就是在這些相似過程中發現的。你認爲呢?

  Farhana:我想這種方式對我更有用。如果我能通過分析問題和歸納方案得出設計模式,我就不用死記那些設計模式和定義了。請按照你的方式繼續。

 一個常見的設計問題和它的解決方案

  Shubho: 讓我們考慮下面的場景:

  我們房間裏有些電器(電燈,風扇等)。這些設備按照某些方式佈局,並由開關控制。任何時候你都能替換或排查一個電器而不用碰到其他東西。例如,你可以換一個電燈而不需要換開關。同樣,你可以換一個開關或排查它而不需要碰到或替換相應的電燈或風扇;甚至你可以用把電燈連接到風扇的開關上,把風扇連到電燈的開關上,而不需要碰到開關。

 風扇和電燈的兩種不同開關,一個普通點,另一個別致點

  Farhana: 是的,但就是這樣子,對嗎?

  Shubho: 是的,確實如此,就該如此佈局。當不同東西聯繫在一起時,它們應該按照一定方式聯繫:修改或替換一個系統時不會影響到另一個,或者說即便有,也應該最小化。這能夠讓你的系統易於管理,且成本低。想想一下,如果改一下房間裏的燈同時需要改開關,你會樂意在你房子上花錢並安裝這個系統嗎?

  Farhana: 當然不會。

Shubho: 現在,讓我們思考一下電燈或風扇如何連接到開關上才能達到改變一個不會影響到另一個。你認爲該如何?

Farhana: 用電線!

  Shubho: 很好。把電燈/風扇和開關聯繫到一起的是電線和電器佈局。我們可以它們看做不同系統間相互聯繫的橋樑。其基本的思想是,一個事物不能和另一外一個事物直接聯繫。當然啦,它們應當通過某些橋樑或接口聯繫在一起。用軟件術語來說,這叫鬆耦合

  Farhana: 我知道了。

  Shubho: 現在,讓我們嘗試推斷在電燈/風扇和開關例子中的幾個關鍵問題,並嘗試推斷它們是如何設計並聯系起來的。

  Farhana: 好,我們試一下。

  例子中我們有開關,可能有幾種開關,如普通的開關,漂亮的開關,但通常來說它們還是開關,並且每種開關都能夠打開和關閉。

  所以下面我們會有一個開關基類Switch

  public class Switch { public void On() { //打開開關 } public void Off() { //關閉開關 } }

  接下來我們可以有一些具體的開關,例如一個漂亮開關,一個普通開關等等,當然,我們會讓類FancySwitchNormalSwitchnd繼承類Switch

  public class NormalSwitch : Switch { } public class FancySwitch : Switch { }

  這裏的兩個具體類有自己的特徵和行爲,只是此時此刻,我們簡單化以下。

  Shubho: 非常棒,接下來電燈和風扇怎麼辦?

 Farhana: 我試試. 根據OODP的開放閉合原則,我們知道只要可能,就應該嘗試抽象,對嗎?

  Shubho:

  Farhana: 跟開關不一樣,風扇和電燈等是兩種不同的事物。對於開關,我們能夠使用一個開關基類Switch,但風扇和電燈是兩個不同的事物,相比定義一個基類,接口可能更合適。一般來說,他們都是電器。所以我們可以定義一個接口,如IElectricalEquipment,作爲對電燈和風扇的抽象,可以嗎?

  Shubho: 可以

  Farhana: 好,每種電器都有些相同的功能。他們能夠打開和關閉。所以接口可能如下:

  public interface IElectricalEquipment { void PowerOn(); //每種電器都能打開 void PowerOff(); //每種電器都能關閉 }

  Shubho: 太好了,你很善於抽象東西。現在我們需要一座橋樑。在現實中,電線是橋樑。在我們對象設計中,開關知道如何打開和關閉電器,電器以某種方式聯繫到開關。這裏我們沒有電線,讓電器連接到開關的唯一方式是封裝。

 Farhana: 是的,但開關不能直接知道風扇或電燈。開關應當知道一個電器IElectricalEquipment能夠打開或關閉。這意味着,ISwitch應該有一個IElectricalEquipment實例,對嗎?

  Shubho: 對,對風扇或電燈的封裝的實例是一個橋樑。所以讓我們修改Switch類以便封裝一個電器:

  public class Switch { public IElectricalEquipment equipment { get; set; } public void On() { //開關打開 } public void Off() { //開關關閉 } }

  Farhana: 明白。讓我們定義真實的電器:風扇和電燈。如我所見,一般來說它們都是電器,所以它們都簡單實現了IElectricalEquipment接口。

  下面是風扇類:

  public class Fan : IElectricalEquipment { public void PowerOn() { Console.WriteLine("風扇打開"); } public void PowerOff() { Console.WriteLine("風扇關閉"); } }

  下面是電燈類:

  public class Light : IElectricalEquipment { public void PowerOn() { Console.WriteLine("電燈打開"); } public void PowerOff() { Console.WriteLine("電燈關閉"); } }

  Shubho:太好了。現在讓開關工作。當開關打開關閉的時候它應當能夠打開關閉電器(它連接到的)

  這裏的關鍵點是:

  當開關按下開時,連接的電器也應該打開。

  當開關按下關時,連接的電器也應該關閉。

  大致的代碼如下:

  static void Main(string[] args) { //構造電器設備:風扇,開關 IElectricalEquipment fan = new Fan(); IElectricalEquipment light = new Light(); //構造開關 Switch fancySwitch = new FancySwitch(); Switch normalSwitch = new NormalSwitch(); //把風扇連接到開關 fanfancySwitch.equipment = fan; //開關連接到電器,那麼當開關打開或關閉時電器應該打開/關閉 fancySwitch.On(); fancySwitch.Off(); //把電燈連接到開關 fancySwitch.equipment = light; fancySwitch.On(); //打開電燈 fancySwitch.Off(); //關閉電燈 }

  Farhana: 明白。開關的On()方法應當內部調用電器的TurnOn()方法,Off()方法應當內部調用TurnOff()方法,所以開關類Switch應如下:

  public class Switch { public IElectricalEquipment equipment { get; set; } public void On() { Console.WriteLine("開關打開"); equipment.PowerOn(); } public void Off() { Console.WriteLine("開關關閉"); equipment.PowerOff(); } }

  Shubho: 很好。這自然允許你把風扇從一個開關接到另一個上。不過你看,反過來也可以。這意味着你可以改變風扇或電燈的開關而不需要碰到風扇或電燈。例如,你可以很輕鬆的把點燈的開關從FancySwitch換到NormalSwitch上,如下:

  normalSwitch.equipment = light; normalSwitch.On(); //打開電燈 normalSwitch.Off(); //關閉電燈

  你看,連接一個抽象電器到一個開關(通過封裝)能夠讓你改變開關和電器而不會對對方產生影響。這個設計是優雅的,良好的。四人組爲該模式取名爲:橋接模式。

  Farhana: 太棒了。我想我明白這個了。從根本上說,兩個系統不應當直接聯繫或依賴與對方。 當然,他們應該聯繫或依賴於抽象(如依賴倒置原則和開放閉合原則所講),所以他們是鬆耦合的,因此我們可以在需要時改變我們的實現而不會對系統其他部分產生過多影響。

  Shubho: 你理解了,親愛的.我們看下橋接模式的定義:

"將抽象部分與實現部分分離,使它們都可以獨立的變化"

 

發佈了36 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章