1. 程式人生 > >Java設計模式1:面向物件程式設計的四大特徵和設計模式的六大原則

Java設計模式1:面向物件程式設計的四大特徵和設計模式的六大原則

這篇博文算是對《設計模式之禪》的讀書筆記。這本書寫得非常好,通俗易懂,強烈推薦!另外,也參考了很多其他的資料,包括http://www.runoob.com/design-pattern/design-pattern-tutorial.html以及網上一些部落格等,再次表示感謝!之後,我會針對幾個重點的設計模式,寫一些程式碼,自己操作熟悉一遍,而其他一些設計模式就在概念上知道即可。

一、設計模式

設計模式(Design Pattern)是一套被反覆使用、多數人知曉的、經過分類的、程式碼設計經驗的總結。使用設計模式的目的是為了程式碼可重用性、讓程式碼更容易被他人理解、保證程式碼可靠性。 設計模式使程式碼編寫真正工程化。設計模式的基礎是面向物件程式設計(Object Oriented Programming,OOP),只要是提及設計模式,肯定都是基於面向物件程式設計的。因此,設計模式也要遵循OOP的基本原則。設計模式不是憑空變出來的。在實際的軟體工程中,很少會一上來就用設計模式。設計模式都是在軟體迭代的過程中,漸漸地根據需求,要對程式碼進行重構,才會用一些設計模式重寫程式碼。歸根到底,設計模式其實就是在一個軟體工程中,徹底地貫徹OOP思想,而拋棄順序的、結構化的程式設計思想。

二、OOP的四個基本特徵

1、抽象

Java關於抽象,最常被討論的就是abstract類和interfaces。

抽象是把系統中需要處理的資料和在這些資料上的操作結合在一起,根據功能、性質和用途等因素抽象成不同的抽象資料型別。每個抽象資料型別既包含了資料,又包含了針對這些資料的授權操作。在面向物件的程式設計中,抽象資料型別是用“類”這種結構來實現的。

2、封裝

封裝從字面上來理解就是包裝的意思,專業點就是資訊隱藏,是指利用抽象資料型別將資料和基於資料的操作封裝在一起,使其構成一個不可分割的獨立實體,資料被保護在抽象資料型別的內部,儘可能地隱藏內部的細節,只保留一些對外介面使之與外部發生聯絡。系統的其他物件只能通過包裹在資料外面的已經授權的操作來與這個封裝的物件進行交流和互動。也就是說使用者是無需知道物件內部的細節,但可以通過該物件對外的提供的介面來訪問該物件。在程式設計中,與封裝密切相關的就是屬性和方法的訪問許可權private,public等。

3、繼承

繼承就是子類繼承父類的特徵和行為,使得子類物件(例項)具有父類的例項域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。繼承的方法允許在不改動原程式的基礎上對其進行擴充,這樣使得原功能得以儲存,而新功能也得以擴充套件。這有利於減少重複編碼,提高軟體的開發效率。繼承是一種強耦合關係。

4、多型

所謂多型就是指程式中定義的引用變數所指向的具體型別和通過該引用變數發出的方法呼叫在程式設計時並不確定,而是在程式執行期間才確定,即一個引用變數倒底會指向哪個類的例項物件,該引用變數發出的方法呼叫到底是哪個類中實現的哪個方法,必須在由程式執行期間才能決定。

要注意,對於面向物件而言,多型分為編譯時多型

執行時多型這兩個內容。其中編譯時多型是靜態的,主要是指方法的過載,它是根據引數列表的不同來區分不同的函式,通過編譯之後會變成兩個不同的函式。這個時候,在編譯時候已經知道要執行哪個函數了。而執行時多型(其實就是動態繫結)是動態的。他是指在執行期間(而不是編譯期間)判斷所引用物件的實際型別,並且根據其實際型別呼叫相應實際使用的方法。我們其實一般習慣上所說的多型,大部分時候都指的是執行時多型。在Java中,有兩種形式可以實現多型,繼承和介面。

(1)編譯時多型

是通過方法過載實現的。過載,是指允許存在多個同名函式,而這些函式的引數表不同(或許引數個數不同,或許引數型別不同(就算是在一個繼承鏈上下的型別,也認為是不同的),或許兩者都不同)。其實嚴格來說,過載的概念並不屬於“面向物件程式設計”,過載的實現是:編譯器根據函式不同的引數表,對同名函式的名稱做修飾,然後這些同名函式就成了不同的函式(至少對於編譯器來說是這樣的)。

(2)執行時多型

是通過覆蓋(重寫)實現的,也就是override。覆蓋,是指子類重新定義父類的函式。方法覆蓋需要子類方法和父類方法的名稱、引數型別和返回型別都完全一致(其實返回型別不一定要一致,子類的方法返回型別比父類縮小也允許)。一般可以在子類的覆蓋的方法前面加上@override來保證這個方法確實是覆蓋。使用父類引用指向子類物件,再呼叫某一父類中的方法時,不同子類會表現出不同結果。如果通過一個父類的引用來呼叫某方法,實際上他會對應到記憶體中真正的物件,他會判斷記憶體中真正的物件是子類物件還是父類物件,然後判斷要呼叫哪個方法。查詢順序是先在子類中找,有就使用,沒有就在父類中找,有就使用,再沒有就報錯了。

三、設計模式的六大原則(SOLIDD)

1、開閉原則(Open Close Principle,OCP)

OCP的意思是,軟體應該對擴充套件開放,對修改關閉。也就是說,在程式需要進行拓展的時候,不能去修改原有的程式碼,而是應該通過擴充套件,實現一個熱插拔的效果。OCP是最基礎的一個原則,後面的另外五個原則,其實都是開閉原則的具體形態,都是為了實現開閉原則的工具和方法。

2、單一職責原則(Single Responsibility Principle,SRP),有些地方也叫合成複用原則(Composite Reuse Principle,CRP)

在軟體系統中,一個類(大到模組,小到方法)承擔的職責越多,它被複用的可能性就越小,而且一個類承擔的職責過多,就相當於將這些職責耦合在一起,當其中一個職責變化時,可能會影響其他職責的運作,因此要將這些職責進行分離,將不同的職責封裝在不同的類中,即將不同的變化原因封裝在不同的類中,如果多個職責總是同時發生改變則可將它們封裝在同一類中。也就是所謂的“有且僅有一個原因導致類的變更”。他要求儘量使用合成/聚合的方式,而不是使用繼承。單一職責原則是實現高內聚、低耦合的指導方針,它是最簡單但又最難運用的原則,需要設計人員發現類的不同職責並將其分離,而發現類的多重職責需要設計人員具有較強的分析設計能力和相關實踐經驗。

3、里氏替換原則(Liskov Substitution Principle,LSP)

任何基類(父類)可以出現的地方,子類一定可以出現。LSP 是繼承複用的基石,只有當子類(派生類)可以替換掉基類,且軟體單位的功能不受到影響時,基類才能真正被複用,而派生類也能夠在基類的基礎上增加新的行為。其實這裡麵包含了四層意思:

(1)子類必須完全實現父類的方法;

(2)子類可以有自己的個性,對於LSP來說,子類可以替換掉父類,但是反之不行,父類不一定能勝任子類的工作;

(3)過載或實現父類方法時輸入引數可以被放大範圍。方法中的輸入引數被稱為前置條件,而返回就是後置條件。在軟體開發中,有一個契約優先的原則。這是因為軟體開發是一個很多人蔘與的大工程,必須要約定一些大家都必須遵守的契約才能通力合作進行開發。這種設計方法叫做契約優先設計,這個設計思想是和LSP融合在一起的。LSP規定了一個契約,就是通過父類和介面設計。前置條件就是你要讓我執行,必須滿足某個條件。後置條件就是我執行完了,必然滿足某個條件。

為了實現子類可以替換父類,只有兩種情況。對於覆蓋(重寫)來說,當然是可以通過動態繫結完全進行替換的。對於過載來說,子類某個方法過載了父類的某個方法,這個子類的方法一範圍一定要比父類擴大而不是縮小。

(4)覆蓋或實現父類方法時返回結果可以被縮小範圍。

4、介面隔離原則(Interface Segregation Principle,ISP)

這個原則的意思是:使用多個隔離的介面,比使用單個介面要好。也就是說,儘量建立多個單一介面,而不是一個臃腫龐大的介面。它還有另外一個意思是:降低類之間的耦合度。由此可見,其實設計模式就是從大型軟體架構出發、便於升級和維護的軟體設計思想,它強調降低依賴,降低耦合。

5、依賴倒轉原則(Dependence Inversion Principle,DIP)

這個原則是開閉原則的基礎,具體內容:針對介面程式設計,依賴於抽象而不依賴於具體。他主要有兩個方面的內容:

(1)高層次的模組不應該依賴於低層次的模組,他們都應該依賴於抽象。當高層的模組不依賴於低層的模組時,這些高層模組就很容易在不同的環境中複用。其實直觀上來說,就是高層次模組在程式設計的時候,應該使用的是低層次模組的抽象,也就是介面等,就算低層次模組的具體實現改變了,高層次的模組也不需要改動。

(2)抽象不應該依賴於具體實現,具體實現應該依賴於抽象。即使實現細節不斷變動,只要抽象不變,使用者程式就不需要變化。

6、迪米特法則,又稱最少知道原則(Demeter Principle,DP)

最少知道原則是指:一個實體應當儘量少地與其他實體之間發生相互作用,使得系統功能模組相對獨立。