幾天一直在研究著一些設計模式,作為初學者,我想很多人可能都會有這樣的感覺,就是很多設計模式看上去都大同小異。那是因為我們並沒有過多的項目經驗,因此並不能想象到何處應該應用何種設計模式。
我在網上看過很多人有這樣一種觀念,就是說設計模式不要學,尤其是學生不要學。其實我不贊同這種說法,因為我一直認為理論和實踐應該是共同進步的。也許我的這個觀點不具有特別的權威性。但是有一點是一定的,我們可以不懂設計模式,但我們一定要熟悉設計原則。正是抱著這個想法,因此,我想從初學者的角度,把設計模式的幾個設計原則系統地來說一下。
首先,我們要先明白為什麼要應用設計模式。有一種這樣的說法,沒有學過設計模式,你就永遠都無法理解,甚至說根本就不會理解什麼是物件導向,為什麼要物件導向。我願意對物件導向的優點用這一句話來總結,設計模式(或者說是物件導向)的根本目的是為了重用和應對變化。在這裡,《大話設計模式》上的活字印刷的例子非常經典地解釋了物件導向的優點。我來大概概括一下這個例子:
曹操雅興作詩一首:喝酒唱歌,人生真爽。於是記錄師就刻了一晚上的字把這八個字給記錄了下來。第二天,曹操改變注意把詩改成對酒唱歌,人生真爽。於是記錄師不得不把這八個字都毀掉,然後重刻了八個字....最後,曹操反覆修改改成對酒當歌,人生幾何。期間,記錄師只能不停地毀掉,重做...耗費的人力勞力之大,我們可以想象。但是我們想象活字印刷,記錄師完全可以把每個字來刻,即使修改了,記錄師也只需要新做幾個字與原來的字組合到一起(應對變化),而且被取代的字將來也可以得到應用(重用性)。
這是現實中的例子,我們再想想程式中的例子。我相信每個程式員都應該聽過這樣一個概念叫組件。每個公司都有著他的組件庫。一家想要發展下去的公司一定要有了一個龐大而且健壯的組件庫。我們舉一個最常見的例子,資料訪問組件。我們寫代碼時,尤其是開發一個網站時,我們可以發現,我們寫的很多代碼都是重複代碼,其實說白了,就是在不停地和資料庫打交道,進行著增刪改查的操作。於是,很多公司就把這部分給提取了出來,做成專門的資料訪問組件,然後程式員只需要訪問這個資料訪問組件,一般來說是向這個資料訪問組件的方法中傳入SQL語句或者是儲存結構名稱,再到最近可能傳入LINQ語句,然後由這個資料訪問組件和資料庫之間來打交道,這樣就節省了很多人力和物力。其實,這也就是我們通常所說的分層。我們來總結這個組件的作用,其實也就無非是為了重用和今後的應對變化。所以一切設計模式和設計原則也是應對著這兩點來展開的。
因此,在應用設計模式上有兩個極端。第一種就是不會用設計模式,這點的人很多。就是根本就不知道用設計模式,做一個項目,就是堆積代碼,用asp.net來舉例子,就是把所有的代碼都寫到.aspx.cs中,這樣的缺點我不說大家也知道,就是非常不容易修改,而且他寫出的代碼,重用性非常差。第二種就是過分地應用設計模式,大家也許都覺得設計模式應用地越多,這樣的項目不是越好嗎?其實不是這樣的,還是我剛才說的那句話,設計的目的是為了重用和應對變化。如果你設計的部分根本不會發生變化,而且根本沒有重用性,這樣的設計不僅使整個項目變得龐大,同時也會影響程式的效率。
這點也是網上有些觀點說學生不適合學設計模式的原因,因為作為學生,沒有企業實際的項目經驗,根本無法很好地找出變化點和重用點,所以極易造成設計過度或者是該設計的地方沒有應用合適的模式。但是,設計原則無論在何處都是應該遵守的。
由於時間原因,今天只來介紹一下設計原則中的第一個:單一職責原則(SRP)。
從名字上就可以看出個大概意思。就是說每個類只有一個職責。這裡的職責,我們要把它理解為,產生變化的原因。把這兩句話綜合起來,就是GOF的話:每個類,只應該有一個能夠引起他變化的原因。
我們仍然從物件導向的兩個角度來分析單一職責原則的目的。首先是重用,我們應該可以想到,一個類的功能越多,那麼這個類的可重用性必然越差。舉例子,一個做蛋糕的機器,比如我們把和面,定型,加溫,冷卻,全都放到同一個機器中,那麼這個機器只可以做出一種蛋糕。但是如果我們把這些功能都分到不同的機器中,有的機器和面,有的機器定型...這樣排列組合,我們應該可以想到。比如說先和面,再加溫,定型,冷卻,就可以做出一種新的蛋糕。甚至可以做出各種各樣不是蛋糕的東西。然後我們想應對變化,我們想在這個新的機器中加入摻雜雞蛋的功能,如果是一台功能完全的機器,我們只能把這台機器拆掉,然後重構。成本可想而知。如果功能分開呢,我不再多說.....
軟體也是一樣,有些人可能對我的應對變化這點產生質疑。說蛋糕機只能拆掉重做,但是一個類只需要添些代碼就可以啦!這個問題我們留一下,下次說到OCP的時候大家就明白了。
從SRP,我來簡單的擴充一個設計模式。其實設計模式,就是用各種模式來達到設計原則的目的罷了。我要說的是迭代器模式。
關於迭代,我相信每個學過進階語言的人都不會陌生,無論是JAVA還是.net。java中的for(:)和.net中的foreach(in)都是迭代器模式的一種封裝體現。
今天只是簡單地說下,以後再來仔細講下這個,引用GOF的解釋。迭代器模式提供一種方法順序訪問一個彙總對象中各個元素,而又不暴露該對象的內部表示。
我們來看下迭代器模式的結構圖:
這裡具體的英文解釋我就不說了,我來簡單說一下兩個介面的作用:
1. IList是一個集合類的介面,我在裡面唯寫了一個方法,就是擷取某特定集合的Iterator。
2. IIterator的作用是規定了迭代器的一些必要方法。
這裡,講Iterator和List分開就是一個單一職責原則的體現。List只是一個容器,至於我們要對這個容器進行什麼操作,不應該屬於這個類所管轄的內容。因此,我們就把這個功能分出了一個新的迭代器的類來完成迭代的工作。比如說現在我們是對這個List類來順序迭代。可是有一天,我突然想對List逆序迭代了,那麼我們只需要增加一個逆序迭代器的類,就可以了。這就單一職責原則的易於變化。另外,比如現在我已經寫好的順序迭代的類,明天,我又寫了一個Arraylist的容器類,同樣要順序迭代,那麼我們完全可以重複應用順序迭代器這個類,這就是單一職責原則的可重用性。
談到這裡,接近尾聲,最後再來提一下,我之前寫過的介面,介面的設計一定要遵循的就是單一職責原則。我之前說,介面不能設計太大,也不能設計太小。我們用這句話再來對單一職責原則做下總結。單一職責,不是說我們把職責分得越細越好。而是要時刻記住,我們應用設計模式的目的,往具體了說,分類的目的不能為了分類而分類,類的劃分是為了封裝,而分類的基礎是抽象。這裡的職責,指的不是有幾個功能,而是有幾個能夠引發類發生變化的原因。
好了,今天就寫到這,不足之處,希望大家多多指教。謝謝。