181
技術社區[雲棲]
《設計模式》學習筆記1——七大麵向對象設計原則
前言
根據這一次的學習計劃,係統學習設計模式之前,先係統學習和理解設計原則。麵向對象設計原則有如下幾類。
原則一:單一職責原則
這是麵向對象最簡單的原則,對於定義,引用書中所說:
單一職責原則(Single Responsibility Principle, SRP):一個類隻負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該隻有一個引起它變化的原因
這裏最重要的地方,我個人覺得應該是一個功能領域
這一句。
設計的前提是思考,隻有進行了思考才能談得上設計,所以實際設計過程中最重要的還應該是思考究竟哪些才是一個功能領域
,也隻有確定了這個,才能真正的遵循單一職責原則。
這個原則讓我想起最近重構中一個功能設計的問題,很明顯一開始的設計並沒有遵循單一職責原則。
這項功能涉及到三個子係統A、B、C,我們需要從A係統向B係統發送http請求,發送過程中帶了兩個參數,一個是具體對象數據obj,一個是動態獲取的B係統向C係統發送請求的請求url等相關信息。
我們當時是在A係統中寫了一個httpService來處理向B係統發送請求,在一個方法中同時做了這樣三件事:
1. 封裝obj數據對象;
2. 動態獲取B向C發送請求的相關請求信息;
3. 正式向B發送請求;
很明顯的,這個類的這個方法看起來做的都是跟發送請求相關的事,但是仔細分析後會發現其實引起這個方法改變的原因至少會有三個,因此至少應該把這三件事分為三個方法來處理。
原則二:開閉原則
開閉原則引用書中的定義如下:
開閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應盡量在不修改原有代碼的情況下進行擴展
這裏最重要的應該是軟件實體
四個字,隻有正確的理解了何為軟件實體,才能更好的用好開閉原則,書中對軟件實體定義如下:
在開閉原則的定義中,軟件實體可以指一個軟件模塊、一個由多個類組成的局部結構或一個獨立的類。
對於開閉原則,我個人理解就是說,可以繼承或實現某個類,在這個類的基礎上增加功能,但是不能直接去改這個類。
然而,在實際實施的過程中顯然是不太容易做到的,這通常需要一定的開發功力,可能還需要先遵循其他一些原則,這個原則才會具有實現的可能。
就比如上邊說的單一職責原則,如果沒有非常好的遵循單一職責原則,各種功能糅合在一起,那麼必然在後期拓展的過程中難以做到很好的開閉。這時候可能能對拓展開放,卻未必能對修改做到關閉,可能發現很多時候必須修改原本的類才行。
同樣是上邊舉例的那個功能,根據單一職責原則,我們把三件事分到了三個方法中實現,但是依然存在著其他的問題。
就比如封裝數據對象這件事,由於很多個業務公用一個請求,在參數個數固定的情況下,不同的業務數據使用的數據對象封裝就必然會有所不同。
當我們用一個方法處理這些數據封裝的時候,就必須使用若幹個if/else這樣的判斷語句,那麼一旦後邊有新業務加入或者舊業務需要修改,必然需要改動這個方法和這個類,所以更好的辦法應該是針對不同業務的不同數據使用不同的方法或者類來進行處理。
原則三:裏氏替換原則
裏氏替換原則引用書中的定義如下:
裏氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類) 的地方必須能透明地使用其子類的對象
在我目前記下的三個原則中,這個恐怕是我覺得最好理解也是最容易做到的了,因為平常開發的過程中幾乎時時刻刻都在寫著接口和實現類。
而這個原則簡單點理解,就是說用接口或者其他類型的父類調用的任何一個方法,都必須要能用這個父類的任何一個子類進行調用;任何一個用父類作為參數的地方,要也都能用任何一個子類作為參數。
裏氏替換,其實可以理解為就是用子類代替父類。
原則四:依賴倒轉原則
依賴倒轉原則引用書中的定義如下:
依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對接口編程,而不是針對實現編程
這個原則和上一個原則乍一看起來我覺得好像是雷同了,但是仔細分析一下後發現其實並沒有。
裏氏替換原則更具體點理解,說的是父類和子類在各自定義的時候應該遵循的一種原則,重點在於父類和子類的定義。
而依賴倒轉原則說的則應該是如何更合理使用父類和子類,重點在於如何使用。
但是這兩個原則說的都是父類和子類的問題,因此很顯然也有必然的聯係,隻有遵循裏氏替換原則合理的定義了父類和子類,才可能更合理的遵循依賴倒轉原則。
因此這也涉及到一個問題,子類雖然從語法上來說可以有自己的對外開放的方法,那麼是否應該提供這樣的方法呢?
很顯然的,如果要完全遵循依賴倒轉原則,子類就不應該定義自己的對外開放的方法,否則針對接口編程的時候,那子類的那些對外開放的特有方法就成了擺設。
原則五:接口隔離原則
接口隔離原則引用書中的定義如下:
接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。
對於這個原則,我倒覺得他更像是單一職責原則的補充,或者說是一個更小維度的單一職責原則,因為我覺得接口實際上也可以理解成單一職責原則中說的某個功能領域
。
而“使用多個專門的接口而不是一個總接口”,這也直接體現了單一職責,隻不過這裏可能是更小維度的單一職責,目的是為了避免一個接口中過多的功能造成依賴於他的客戶端做出一些不必要的工作。
那麼如果再結合前兩個原則來說,我更覺得他們就是一個整體,首先裏氏替換原則概括的說明了父類和子類應該以何種關係定義,然後依賴倒轉原則又說了父類和子類應該如何使用,再然後這裏的接口隔離原則就更進一步具體說明了父類接口應該如何更好的定義。
原則六:合成複用原則
合成複用原則引用書中的定義如下:
合成複用原則(Composite Reuse Principle, CRP):盡量使用對象組合,而不是繼承來達到複用的目的
根據我的理解,複用一般指的是自己本身不具備的方法,但可以拿來使用,而實現方式通常是組合、聚合和繼承。
繼承指的是,在父類中寫的方法被子類繼承後,子類不需要再寫一遍這個方法,子類的對象就可以調用。
組合指的是,聲明一個類的時候,把另一個類以屬性的方式聲明,然後在這個類的對象中便包含了那個類的對象,然後這個類的對象中就可以調用那個類中的方法,從而實現自己不用定義,當能實現某些功能。
而聚合通常是說把另一個類的對象以參數的方式傳進來,然後這個類的對象的方法中也就可以調用參數對象的方法,這樣也實現了自己不定義,但能實現某些功能。
以上三種方式都能實現代碼和功能的福永,減少了重複代碼,但是繼承會破壞類的封裝性,把父類的實現細節暴露給子類,同時如果父類聲明為不可被繼承,那麼還不能被複用,這些都是非必要,不建議使用繼承複用的原因。
相反的,組合和聚合就更加的靈活,具體的實現也不會暴露給其他組合的類,因此建議使用組合和聚合實現代碼和功能的複用,也就是合成複用原則。
原則七:迪米特法則
迪米特法則引用書中的定義如下:
迪米特法則(Law of Demeter, LoD):一個軟件實體應當盡可能少地與其他實體發生相互作用
迪米特法則又稱為最少知識原則、最少知道原則,說白了,就是一個類中定義的屬性、方法這些應當嚴格定義訪問權限,隻暴露必須暴露出來的,而私有不必要暴露出來的。
之所以要這樣做,是因為當一個類暴露出過多的內容在外邊時,一方麵會使引起他變化的因素變多,情況變得複雜,另一方麵也會使得使用這個類的時候造成選擇的困難。
最後更新:2017-11-17 19:04:13