看老外程序員如何向妻子解釋設(shè)計(jì)模式
設(shè)計(jì)模式是什么?
Shubho:通過我們關(guān)于面向?qū)ο笤O(shè)計(jì)原則(OODP,即SOLID原則)的對話,我想你已經(jīng)對面向?qū)ο笤O(shè)計(jì)原則(OODP)有了基本的認(rèn)識。希望你不要介意我把對話分享到博客上。你可以在這找到它:<如何向妻子解釋OOD>.
設(shè)計(jì)模式是這些原則在某些特定公共場景下標(biāo)準(zhǔn)化的應(yīng)用,接下來讓我們通過一些例子學(xué)習(xí)什么是設(shè)計(jì)模式。
Farhana: 當(dāng)然,我喜歡例子。
Shubho: 讓我們以汽車為例討論一下。汽車是一個(gè)很復(fù)雜的對象,由成千上萬的其它對象組成,如發(fā)動機(jī),車輪,方向盤,車座,車體等等其他不同的部分或部件。

汽車部件
當(dāng)裝配汽車時(shí),制造商需要集中并裝配這些更小的自成汽車子系統(tǒng)的不同部件。而這些不同的小部件同樣也是復(fù)雜的對象,其它制造商同樣要生產(chǎn)并組裝它們。在生產(chǎn)汽車時(shí),汽車公司并不會為怎么生產(chǎn)組裝這些部件操心(前提是他們要確保這些對象/設(shè)備的質(zhì)量)。當(dāng)然,汽車制造商更加關(guān)心怎么裝配這些不同部件以便能生產(chǎn)不同型號的汽車。

通過遵循不同的設(shè)計(jì),組裝不同的部件,生產(chǎn)不同型號的汽車
Farhana: 汽車制造公司必須有如何生產(chǎn)不同型號汽車的設(shè)計(jì)圖或藍(lán)圖,對嗎?
Shubho: 當(dāng)然,并且這些設(shè)計(jì)都是良好的,他們花費(fèi)大量的時(shí)間和精力來做這些設(shè)計(jì)。一旦設(shè)計(jì)完成,生產(chǎn)汽車就僅僅是照葫蘆畫瓢了。
Farhana: 嗯。如果事先有一些好的設(shè)計(jì),就能在短時(shí)間內(nèi)遵照這些設(shè)計(jì)生產(chǎn)不同產(chǎn)品,并且制造商在每次生產(chǎn)某一個(gè)型號產(chǎn)品時(shí)就不需要重新設(shè)計(jì)或重新發(fā)明車輪,他們只需要按照已有的設(shè)計(jì)辦事就行了。

生產(chǎn)不同型號產(chǎn)品(汽車)的不同設(shè)計(jì)圖
Shubho: 你抓到重點(diǎn)了?,F(xiàn)在假設(shè)我們是軟件生產(chǎn)商,我們使用基于需求而來的不同組件或功能構(gòu)建各種不同的軟件程序。當(dāng)生產(chǎn)這些不同軟件系統(tǒng)時(shí),我們常常需要為一些不同軟件系統(tǒng)中存在的相同情況開發(fā)代碼,對嗎?
Farhana: 是的,在開發(fā)不同軟件程序時(shí)經(jīng)常遇到相同的設(shè)計(jì)問題。
Shubho: 我們嘗試使用面向?qū)ο蟮姆绞介_發(fā)軟件,并嘗試應(yīng)用OOPD來讓代碼能易于維護(hù),可復(fù)用,可擴(kuò)展。無論什么時(shí)候,當(dāng)我們遇到這些設(shè)計(jì)問題時(shí),如果我們有一組經(jīng)過謹(jǐn)慎開發(fā),良好測試的對象以供使用會不會更好呢?
Farhana: 是的,這樣能夠節(jié)省時(shí)間,生產(chǎn)出更好的軟件,且利于以后維護(hù)。
Shubho: 很好!從設(shè)計(jì)上來說,它的好處是你不需要開發(fā)那些對象。經(jīng)過多年發(fā)展,人們已經(jīng)遇到過一些類似的設(shè)計(jì)問題,并已經(jīng)形成有一些公認(rèn)的,良好的已標(biāo)準(zhǔn)化的設(shè)計(jì)方案。我們稱之為設(shè)計(jì)模式。
我們一定好感謝四人組,他們在《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖O(shè)計(jì)》中總結(jié)出了23種基本的設(shè)計(jì)模式。四人組由Erich Gamma, Richard Helm, Ralph Johnson, 和John Vlissides組成。實(shí)際中有很多面向?qū)ο笤O(shè)計(jì)模式,但這23種模式被公認(rèn)為是所有其他設(shè)計(jì)模式的基礎(chǔ)。
Farhana: 我能發(fā)明一個(gè)新的模式嗎?這可能嗎?
Shubho: 當(dāng)然,親愛的,為什么不能呢?!設(shè)計(jì)模式不是由科學(xué)家發(fā)明創(chuàng)造的。它們是被發(fā)現(xiàn)找到的。這意味著任何通用問題場景中都有一些好的設(shè)計(jì)方案在那。如果我們能夠指出一個(gè)能夠解決一個(gè)新的設(shè)計(jì)相關(guān)問題的面向?qū)ο笤O(shè)計(jì),那么這將會是一個(gè)由我們定義的新的設(shè)計(jì)模式。誰知道呢?!如果我們發(fā)現(xiàn)找到一些設(shè)計(jì)模式,或許將來有一天人們會稱我們?yōu)槎私M,哈哈。
Fahana: :)
我們將如何學(xué)習(xí)設(shè)計(jì)模式?
Shubho: 我一直認(rèn)為例子是學(xué)習(xí)的最好途徑。在我們的學(xué)習(xí)方法中,我們不會先討論理論后討論實(shí)現(xiàn)。我認(rèn)為這是很糟糕的方式。設(shè)計(jì)模式不是基于理論的發(fā)明。事實(shí)上,問題場景首先出現(xiàn),其次是基于這些問題的來龍去脈和需求,然后是一些設(shè)計(jì)方案的演化,最后其中的一些被標(biāo)準(zhǔn)化為模式。所以對每一個(gè)我們討論的設(shè)計(jì)模式,我們將嘗試?yán)斫獠⒎治鲆恍┈F(xiàn)實(shí)生活中的例子,然后一步步嘗試歸納一個(gè)設(shè)計(jì),并最后總結(jié)一些與某些模式匹配設(shè)計(jì)。設(shè)計(jì)模式就是在這些相似過程中發(fā)現(xiàn)的。你認(rèn)為呢?
Farhana:我想這種方式對我更有用。如果我能通過分析問題和歸納方案得出設(shè)計(jì)模式,我就不用死記那些設(shè)計(jì)模式和定義了。請按照你的方式繼續(xù)。
一個(gè)常見的設(shè)計(jì)問題和它的解決方案
Shubho: 讓我們考慮下面的場景:
我們房間里有些電器(電燈,風(fēng)扇等)。這些設(shè)備按照某些方式布局,并由開關(guān)控制。任何時(shí)候你都能替換或排查一個(gè)電器而不用碰到其他東西。例如,你可以換一個(gè)電燈而不需要換開關(guān)。同樣,你可以換一個(gè)開關(guān)或排查它而不需要碰到或替換相應(yīng)的電燈或風(fēng)扇;甚至你可以用把電燈連接到風(fēng)扇的開關(guān)上,把風(fēng)扇連到電燈的開關(guān)上,而不需要碰到開關(guān)。
電器:風(fēng)扇和電燈

風(fēng)扇和電燈的兩種不同開關(guān),一個(gè)普通點(diǎn),另一個(gè)別致點(diǎn)
Farhana: 是的,但就是這樣子,對嗎?
Shubho: 是的,確實(shí)如此,就該如此布局。當(dāng)不同東西聯(lián)系在一起時(shí),它們應(yīng)該按照一定方式聯(lián)系:修改或替換一個(gè)系統(tǒng)時(shí)不會影響到另一個(gè),或者說即便有,也應(yīng)該最小化。這能夠讓你的系統(tǒng)易于管理,且成本低。想想一下,如果改一下房間里的燈同時(shí)需要改開關(guān),你會樂意在你房子上花錢并安裝這個(gè)系統(tǒng)嗎?
Farhana: 當(dāng)然不會。
Shubho: 現(xiàn)在,讓我們思考一下電燈或風(fēng)扇如何連接到開關(guān)上才能達(dá)到改變一個(gè)不會影響到另一個(gè)。你認(rèn)為該如何?
Farhana: 用電線!
Shubho: 很好。把電燈/風(fēng)扇和開關(guān)聯(lián)系到一起的是電線和電器布局。我們可以它們看做不同系統(tǒng)間相互聯(lián)系的橋梁。其基本的思想是,一個(gè)事物不能和另一外一個(gè)事物直接聯(lián)系。當(dāng)然啦,它們應(yīng)當(dāng)通過某些橋梁或接口聯(lián)系在一起。用軟件術(shù)語來說,這叫“松耦合”。
Farhana: 我知道了。
Shubho: 現(xiàn)在,讓我們嘗試推斷在電燈/風(fēng)扇和開關(guān)例子中的幾個(gè)關(guān)鍵問題,并嘗試推斷它們是如何設(shè)計(jì)并聯(lián)系起來的。
Farhana: 好,我們試一下。
例子中我們有開關(guān),可能有幾種開關(guān),如普通的開關(guān),漂亮的開關(guān),但通常來說它們還是開關(guān),并且每種開關(guān)都能夠打開和關(guān)閉。
#p#
所以下面我們會有一個(gè)開關(guān)基類Switch:
- public class Switch {
- public void On()
- { //打開開關(guān) }
- public void Off()
- { //關(guān)閉開關(guān) }
- }
接下來我們可以有一些具體的開關(guān),例如一個(gè)漂亮開關(guān),一個(gè)普通開關(guān)等等,當(dāng)然,我們會讓類FancySwitch和NormalSwitchnd繼承類Switch:
- public class NormalSwitch : Switch {
- }
- public class FancySwitch : Switch {
- }
這里的兩個(gè)具體類有自己的特征和行為,只是此時(shí)此刻,我們簡單化以下。
Shubho: 非常棒,接下來電燈和風(fēng)扇怎么辦?
Farhana: 我試試. 根據(jù)OODP的開放閉合原則,我們知道只要可能,就應(yīng)該嘗試抽象,對嗎?
Shubho: 對
Farhana: 跟開關(guān)不一樣,風(fēng)扇和電燈等是兩種不同的事物。對于開關(guān),我們能夠使用一個(gè)開關(guān)基類Switch,但風(fēng)扇和電燈是兩個(gè)不同的事物,相比定義一個(gè)基類,接口可能更合適。一般來說,他們都是電器。所以我們可以定義一個(gè)接口,如IElectricalEquipment,作為對電燈和風(fēng)扇的抽象,可以嗎?
Shubho: 可以
Farhana: 好,每種電器都有些相同的功能。他們能夠打開和關(guān)閉。所以接口可能如下:
- public interface IElectricalEquipment {
- void PowerOn(); //每種電器都能打開
- void PowerOff(); //每種電器都能關(guān)閉
- }
Shubho: 太好了,你很善于抽象東西?,F(xiàn)在我們需要一座橋梁。在現(xiàn)實(shí)中,電線是橋梁。在我們對象設(shè)計(jì)中,開關(guān)知道如何打開和關(guān)閉電器,電器以某種方式聯(lián)系到開關(guān)。這里我們沒有電線,讓電器連接到開關(guān)的唯一方式是封裝。
Farhana: 是的,但開關(guān)不能直接知道風(fēng)扇或電燈。開關(guān)應(yīng)當(dāng)知道一個(gè)電器IElectricalEquipment能夠打開或關(guān)閉。這意味著,ISwitch應(yīng)該有一個(gè)IElectricalEquipment實(shí)例,對嗎?
Shubho: 對,對風(fēng)扇或電燈的封裝的實(shí)例是一個(gè)橋梁。所以讓我們修改Switch類以便封裝一個(gè)電器:
- public class Switch {
- public IElectricalEquipment equipment {
- get;
- set;
- }
- public void On() {
- //開關(guān)打開
- }
- public void Off() {
- //開關(guān)關(guān)閉
- }
- }
Farhana: 明白。讓我們定義真實(shí)的電器:風(fēng)扇和電燈。如我所見,一般來說它們都是電器,所以它們都簡單實(shí)現(xiàn)了IElectricalEquipment接口。
下面是風(fēng)扇類:
- public class Fan : IElectricalEquipment {
- public void PowerOn() {
- Console.WriteLine("風(fēng)扇打開");
- }
- public void PowerOff() {
- Console.WriteLine("風(fēng)扇關(guān)閉");
- }
- }
下面是電燈類:
- public class Light : IElectricalEquipment {
- public void PowerOn() {
- Console.WriteLine("電燈打開");
- }
- public void PowerOff() {
- Console.WriteLine("電燈關(guān)閉");
- }
- }
Shubho:太好了?,F(xiàn)在讓開關(guān)工作。當(dāng)開關(guān)打開關(guān)閉的時(shí)候它應(yīng)當(dāng)能夠打開關(guān)閉電器(它連接到的) 。
這里的關(guān)鍵點(diǎn)是:
當(dāng)開關(guān)按下開時(shí),連接的電器也應(yīng)該打開。
當(dāng)開關(guān)按下關(guān)時(shí),連接的電器也應(yīng)該關(guān)閉。
大致的代碼如下:
- static void Main(string[] args) {
- //構(gòu)造電器設(shè)備:風(fēng)扇,開關(guān)
- IElectricalEquipment fan = new Fan();
- IElectricalEquipment light = new Light();
- //構(gòu)造開關(guān)
- Switch fancySwitch = new FancySwitch();
- Switch normalSwitch = new NormalSwitch();
- //把風(fēng)扇連接到開關(guān)
- fanfancySwitch.equipment = fan;
- //開關(guān)連接到電器,那么當(dāng)開關(guān)打開或關(guān)閉時(shí)電器應(yīng)該打開/關(guān)閉
- fancySwitch.On();
- fancySwitch.Off();
- //把電燈連接到開關(guān)
- fancySwitch.equipment = light;
- fancySwitch.On(); //打開電燈
- fancySwitch.Off(); //關(guān)閉電燈
- }
Farhana: 明白。開關(guān)的On()方法應(yīng)當(dāng)內(nèi)部調(diào)用電器的TurnOn()方法,Off()方法應(yīng)當(dāng)內(nèi)部調(diào)用TurnOff()方法,所以開關(guān)類Switch應(yīng)如下:
- public class Switch {
- public IElectricalEquipment equipment {
- get;
- set;
- }
- public void On() {
- Console.WriteLine("開關(guān)打開");
- equipment.PowerOn();
- }
- public void Off() {
- Console.WriteLine("開關(guān)關(guān)閉");
- equipment.PowerOff();
- }
- }
Shubho: 很好。這自然允許你把風(fēng)扇從一個(gè)開關(guān)接到另一個(gè)上。不過你看,反過來也可以。這意味著你可以改變風(fēng)扇或電燈的開關(guān)而不需要碰到風(fēng)扇或電燈。例如,你可以很輕松的把點(diǎn)燈的開關(guān)從FancySwitch換到NormalSwitch上,如下:
- normalSwitch.equipment = light;
- normalSwitch.On(); //打開電燈
- normalSwitch.Off(); //關(guān)閉電燈
你看,連接一個(gè)抽象電器到一個(gè)開關(guān)(通過封裝)能夠讓你改變開關(guān)和電器而不會對對方產(chǎn)生影響。這個(gè)設(shè)計(jì)是優(yōu)雅的,良好的。四人組為該模式取名為:橋接模式。
Farhana: 太棒了。我想我明白這個(gè)了。從根本上說,兩個(gè)系統(tǒng)不應(yīng)當(dāng)直接聯(lián)系或依賴與對方。 當(dāng)然,他們應(yīng)該聯(lián)系或依賴于抽象(如依賴倒置原則和開放閉合原則所講),所以他們是松耦合的,因此我們可以在需要時(shí)改變我們的實(shí)現(xiàn)而不會對系統(tǒng)其他部分產(chǎn)生過多影響。
Shubho: 你理解了,親愛的.我們看下橋接模式的定義:
"將抽象部分與實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立的變化"
你看我們的實(shí)現(xiàn)完美遵循該定義。如果你有一個(gè)類設(shè)計(jì)器(如Visual Studio或其他支持該功能的IDE環(huán)境),你會看到類似的如下類圖:

橋接模式類圖
在這里, Abstraction 是開關(guān)基類Switch。 RefinedAbstraction 是具體開關(guān)類 (FancySwitch, NormalSwitch 等等。)。 Implementor 是電器接口IElectricalEquipment。ConcreteImplementorA 和ConcreteImplementorB 是電燈類Light和風(fēng)扇類Fan。
Farhana: 問你個(gè)問題,只是好奇啊。如你所說有很多其他的設(shè)計(jì)模式,為什么你以橋接模式開始呢?有重要原因嗎?
Shubho: 這個(gè)問題很好。是的,我以橋接模式而不以其他開始是因?yàn)橐粋€(gè)理由。我認(rèn)為橋接模式是所有面向?qū)ο竽J降幕A(chǔ)。理由如下:
它教導(dǎo)如何思考抽象,這是面向?qū)ο笤O(shè)計(jì)模式的關(guān)鍵概念。
它實(shí)現(xiàn)了基本的OOD原則。
它容易理解。
如果正確理解該模式,學(xué)習(xí)其他模式會很容易。
Farhana: 你認(rèn)為我理解的對嗎?
Shubho: 我認(rèn)為你理解的非常正確。
Farhana: 那么接下來是什么?
Shubho: 通過理解橋接模式,我們僅僅是開始理解設(shè)計(jì)模式的思想。在我們接下的對話中,我們將會學(xué)習(xí)其他的設(shè)計(jì)模式,我希望你不會覺得它們無聊。
Farhana:不會的,相信我。
原文鏈接:http://www.cnblogs.com/niyw/archive/2011/05/30/2062071.html
【編輯推薦】