程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java設計模式之初學者筆記——設計模式基礎講解

Java設計模式之初學者筆記——設計模式基礎講解

編輯:JAVA綜合教程

Java設計模式之初學者筆記——設計模式基礎講解


前言

最近了解了一下設計模式,起初看的是《大話設計模式》,這本書是用C#語言寫的,覺著挺有意思,其實很多模式我們都已經在用了,卻不知道這就是設計模式。所以後來買了本GOF的《設計模式——可復用面向對象軟件的基礎》打算好好鑽研下。這本書是設計模式的鼻祖,相當權威,書名中說的是“可復用面向對象軟件的基礎”,這是基礎,我對此表示比較震撼。用了三年的面向對象語言居然不了解設計模式,不知道這是基礎,看來也是白活了。我暫時了解到的在此基礎上還有重構等等很多比較高級概念。但這本書顯然超出了我的理解范圍,首先他用的語言是C++,我之前沒接觸過。還有就是這本書僅有248頁,可見語言之簡練,很多地方寫的特別晦澀難懂,沒有堅實的基礎的人還是不推薦這本書了。為了繼續深入的學習,我又買了一本閻宏的《Java與模式》,共1024頁,講述的可謂詳盡。這本書不僅對GOF的23種設計模式做了比較詳細的講解還講了幾個之後出現的設計模式。但對於一些復雜的模式我還是不能很好的理解,或是對一些比較簡單模式也只是知道結構,卻不知道如何應用。總結下來,設計模式對我暫時的工作毫無幫助。但我卻對面對對象有了更加深入的認識,比如之前我一直使用接口,卻不知道為什麼,我現在仍然在使用,但我知道我為什麼這麼做了,之前是無腦的模仿,但在我了解了設計模式之後,我學會了思考。這種提高我覺著是我最大的收獲。

我覺著設計模式光憑死讀書是學不會的,實踐中總結,再實踐,再總結,終有一天我相信我會掌握設計這門藝術,而現在,我需要做的是把理論基礎打好,為了將來更好的實踐它。

該筆記是編者讀過《大話設計模式》、《Java與模式》和部分《設計模式——可復用面向對象軟件的基礎》之後總結出來的初學者筆記,裡面大部分摘自書記中的關鍵性語句,也有部分自己淺顯的見解,不足之處,還請見諒。

鳴謝

特別鳴謝資深程序員蓋超為本筆記提出寶貴意見,完成了審校工作,確保了本筆記的正確性,並為不足和缺陷的地方添加了批注。

 

1、我們為什麼需要設計模式?

每一個模式描述了一個在我們周圍不斷重復發生的問題,以及該問題的解決方案的核心,這樣,你就能一次又一次地使用該方案而不必做重復的勞動。[GOF]

上面這句話指的是城市和建築模式,但他的思想也同樣適用於面向對象的設計模式。所以架構師的英文和建築師是一樣的——Architect。我對上面這句話的理解是這樣的:設計模式就是為了達到代碼復用的目的。(批注:模式是代碼復用的一種方式。具體問題具體分析,需要根據情況選擇對應的模式。比如回收站要用單例模式實現。具體規則的定義可能會用到模板方法。這些需要依照具體是情況來定。)

軟件並不是做好了就永遠不變的,它需要隨時間而增加新的功能,或改變功能,所以需要設計模式來使改動最小卻能實現我們新的功能。所以我們所有的設計模式都是為了更好的復用之前的代碼,在最小程度改變原有代碼的情況下把新的功能加進去,或把某些需要的功能改變。這就是設計模式的初衷。

 

2、設計模式基於哪些設計原則?

 

設計模式是基於一定的原則的,這些原則就是設計原則。所有設計模式都在基於這些設計原則的基礎上去實現代碼復用。所以我認為設計原則是設計模式的基礎。(批注:同時也是設計的目的)

 

1)開-閉原則(Open-Closed Principle 或 OCP):一個軟件實體應當對擴展開放,對修改關閉。[Java與模式]

對於OCP通俗的解釋就是:軟件實體可以被擴展,但不可以被修改。(批注:這個原則非常重要,是設計的核心所在。)

每個軟件推出後都會不定時的進行更新,增加新的功能,若每次增加新的功能時都要大量的修改之前的代碼的話很有可能講之前好用的功能改壞,所以好的設計就是遵循開閉原則,每次增加新功能時只是增加新的類,盡可能少的去修改之前的代碼。

實現開閉原則抽象化是其中的關鍵。[Java與模式]

我認為,開閉原則是總的原則,之後所有的設計原則都是為了更好的實現開閉原則的。因為OCP正是設計模式所追求的達到更好的代碼復用。在盡量少的修改原有的代碼的基礎上,可以增加新的功能、新的模塊。

 

2)單一職責原則(Single Responsibility Principle 或 SRP):就一個類而言,應該僅有一個引起它變化的原因。[大話設計模式](批注:我的理解是面向對象後,能高度抽象 的盡量抽象出來,這樣才符合自然。)

我對於SRP的理解就是,一個類只負責一個功能,這樣可以使程序更利於維護。但“一個功能”我認為是一個模糊的概念,“登陸”是一個功能,“登陸”裡面的“輸入密碼”又是一個功能,輸入密碼之後的“密碼加密”又是一個功能,但怎麼樣才算單一功能呢?這大概需要程序員自己去把握這個度。這個度,我認為就是程序的“粒度”。這裡涉及到一個專業的術語——“粒度”。對於粒度暫且這樣理解,因為我並沒有找到相關資料。

 

3)對可變性的封裝原則(Principle of Encapsulation of Variation 或者 EVP):找到一個系統的可變因素,將之封裝起來。[Java與模式]

[GOF]中對於EVP的解釋:考慮你的設計中什麼可能會發生變化。與通常將焦點放到什麼會導致設計改變的思考方式正好相反,這一思路考慮的不是什麼會導致設計改變,而是考慮你允許什麼發生變化而不讓這一變化導致重新設計。

該原則意味著兩點[Java與模式]:

a、一種可變性不應當散落在代碼的很多角落裡,而應當被封裝到一個對象裡。

b、一種可變性不應當與另一種可變性混合在一起。

 

4)裡氏代換原則(Liskov Substitution Principle 或者 LSP):任何基類可以出現的地方,子類一定可以出現。[Java與模式] 注:基類就是父類(編者注)

[大話設計模式]中的解釋:一個軟件實體如果使用的是一個父類的話,那麼一定適用於其子類,而且它察覺不出父類對象和子類對象的區別。也就是說,在軟件裡面,把父類都替換成他的子類,程序的行為沒有變化。簡單的說,就是子類型必須能夠替換掉它們的父類型。

通俗的來講就是:子類可以擴展父類的功能,但不能改變父類原有的功能。(批注:繼承重寫的過程是一種對父類或接口標准重新實現的過程,這個過程中必須要按照約定好的標准來做。父類的定義就是標准的定義。)

它包含以下4層含義:

子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法。

子類中可以增加自己特有的方法。

當子類的方法重載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬松。(編者注:這裡寬松指的是類型)

當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。

[以上摘自mile的博客]

那麼LSP如何去實現盡量少的更改原有代碼而增加新的功能的呢?

[大話設計模式]是這樣解釋的:只有當子類可以替換掉父類,軟件單元的功能不受影響時,父類才被真正的復用,而子類也能夠在父類的基礎上增加新的行為。由於這種可替換性才使得父類類型的模塊在無需修改的情況下就可以擴展。

 

5)依賴倒轉原則(Dependency Inversion Principle 或者 DIP):

a、抽象不應當依賴於細節,細節應當依賴於抽象[Java與模式]

b、要針對接口編程,不要針對實現編程[GOF]

c、高層模塊不應該依賴底層模塊。兩個都應該依賴抽象[大話設計模式]

 

我對DIP的理解是這樣的:比如聲明一個加密接口Encryption,但它的實現類是MD5加密的EncryptionMD5Impl,也就是EncryptioneMD5 = newEncryptionMD5Impl();然後在程序中調用的加密方法eMD5.encrypt();這裡是MD5加密,但有一天我想用Base64加密了,這種針對於接口的編程就可以在改很少的原有代碼下實現增加新的功能。也就是加一個EncryptionBase64Impl實現Encryption接口,然後其余的地方都不用改。我認為這就是DIP最基本的運用。

 

注:以抽象方式耦合是依賴倒轉原則的關鍵,裡氏代換原則是實現依賴倒轉原則的基礎。[Java與模式]

 

6)接口隔離原則(Interface Segregation Principle 或者 ISP):使用多個專門的接口比使用單一的總接口要好。[Java與模式]

 

我對ISP沒有什麼深刻的認識,就是覺著要把一個大的接口劃分成小的接口,把功能分離,就是ISP。

\

這個就是違反ISP的實例

\

這個是遵守ISP的實例

 

7)組合/聚合復用原則(Composition/Aggregation Reuse Principle 或者 CARP):在一個新的對象裡面使用一些已有的對象,使之成為新對象的一部分;新的對象通過向這些對象的委派達到復用已有功能的目的。[Java與模式]

 

簡言之CARP就是:要盡量使用合成/聚合,盡量不要使用繼承。[Java與模式](這個是開發的一個准則。一般情況都是這樣做的。除非面對的需求是有特殊性的,比如模板方法,這個就是把繼承用到極致的一個典范。)

 

那麼問題來了,為什麼要盡量使用合成/聚合,不使用繼承呢?

我認為,首先,合成/聚合是一種較弱的耦合關系,而繼承確實一種強耦合,使得兩個類之間父類改變子類也隨之變化。其次,繼承沒有合成/聚合靈活,合成/聚合關系可以將功能委派給一個接口,而功能需要改變的時候只需要改變接口的實現類就好了。但繼承的實現卻是靜態的,不能這樣改變。最後繼承破壞了封裝性(書上是這麼說的,但對於此我並沒有太好的理解)。

 

8)迪米特法則(Law of Demeter 或者 LoD):只與你直接的朋友們通信。[Java與模式](這個在實際應用中需要看具體的情況來定,不可以一味的套用。在抽象的基礎上降低耦合性是常見的做法。如果每一個類只有一個朋友類,反而達不到解耦的效果。)

對迪米特法則的解釋:如果兩個類不必彼此直接通信,那麼這兩個類就不應當發生直接的相互關系。如果其中的一個類需要調用另一個類的某一個方法的話,可以通過第三者轉發這個調用。[Java與模式]

我認為LoD就是為了降低類與類之間的耦合,把功能分開,實現模塊化,讓模塊之間通過另一個共通的“朋友”類進行通信,使得將來程序改動的時候只改動某一個模塊,因為模塊之間是通過他們共通的朋友通信的,所以耦合度低,不會影響其他模塊的功能。[Java與模式]中說LoD的主要用意是控制信息的過載。我認為就是模塊化這個意思。

此筆記由崔淼編著


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved