面向对象设计的五项基本原则
文章目錄
- 1.單一職責(zé)原則(SRP: Single Resposibility Principle)
- 2.開放封閉原則(OCP: Open Closed Principle)
- 3.里氏替換原則(LSP: Liskov Substituion Principle)
- 4.依賴倒置原則(DIP: Dependecy Inversion Principle)
- 5.接口分離原則(ISP: Interface Segregation Principle)
- 6.小結(jié)
- 參考文獻
面向?qū)ο笤O(shè)計(OOD)是面向?qū)ο缶幊?#xff08;OOP)必不可少的一個環(huán)節(jié),只有好的設(shè)計,才能保障程序的質(zhì)量。
面向?qū)ο笤O(shè)計的主要任務(wù)就是類的設(shè)計,不少面向?qū)ο?#xff08;OO)的先驅(qū)和前輩已經(jīng)提出了很多關(guān)于類的設(shè)計原則,用于指導(dǎo) OOP,其中就包括類設(shè)計的五項基本原則。
1.單一職責(zé)原則(SRP: Single Resposibility Principle)
專注是一個人的優(yōu)良品質(zhì),同樣,單一職責(zé)也是一個類的優(yōu)良設(shè)計。單一職責(zé)的核心思想:一個類只做好一件事情。
單一職責(zé)原則可以看作是高內(nèi)聚、低耦合在面向?qū)ο笤瓌t上的引申。類的職責(zé)過多,容易導(dǎo)致類間職責(zé)依賴,提高耦合度,降低內(nèi)聚性。通常意義下的單一職責(zé),指的是類只有一種單一功能,不要為類設(shè)計過多的功能,交雜不清的功能會使代碼雜亂,提高程序開發(fā)的難度和系統(tǒng)出錯的概率,降低系統(tǒng)的可維護性。
要舉個體現(xiàn)單一職責(zé)原則的最常見的例子無疑就是STL中的迭代器的設(shè)計。有些人覺得容器跟迭代器的分離是不好的設(shè)計,覺得增加了復(fù)雜度,不如直接把迭代器放在容器里更為簡潔。不過很多人還是不這樣認(rèn)為,因為類的數(shù)量越多并不代表就越復(fù)雜,另外迭代器如果放到容器里面,就會暴露容器的一些內(nèi)部結(jié)構(gòu),不太符合封裝的思想。還有就是可擴展性的問題,因為對容器的訪問遍歷會有多種需求,如果把迭代器隔離開來你可以不修改容器類,再定義些特制的迭代器就行了,這樣不管有什么奇怪的需求只要寫個對應(yīng)的迭代器出來就行了。
2.開放封閉原則(OCP: Open Closed Principle)
開閉原則指的是開放封閉原則,即對擴展開放,對修改封閉。
所謂修改封閉,就是之前設(shè)計好的類,不要去修改。比如刪除掉一個成員函數(shù)、改變成員函數(shù)的形參列表或更改數(shù)據(jù)成員類型等。實現(xiàn)對修改封閉,關(guān)鍵在于抽象化。對一個事物抽象化,實質(zhì)上是對一個事物進行概括、歸納、總結(jié),將其本質(zhì)特征抽象地用一個類來表示,這樣類才會相對穩(wěn)定,無需更改。
所謂擴展開放,就是在不改變已存在的類的前提下可以添加很多功能。一般是通過繼承和多態(tài)來實現(xiàn),如此一來,可以保持父類的原樣,只需在子類中添加些所需的新功能。
“需求總是變化的”,如果遵循開放封閉原則,合理設(shè)計就能封閉變化,使類能夠靈活的擴展所需的功能。
3.里氏替換原則(LSP: Liskov Substituion Principle)
Liskov 替換原則指的是:子類可以替換父類并出現(xiàn)在父類能夠出現(xiàn)的任何地方。這個原則是 Liskov 于1987年提出,它同樣可以從 Bertrand Meyer的 DBC(Design by Contract,按契約設(shè)計)的概念推出。
C++ 語言機制將類的抽象與多態(tài)建立在繼承的基礎(chǔ)上,其實現(xiàn)的方法是面向接口編程:通過提取純虛類(Abstract Class),將公共部分抽象為基類接口或由子類重寫覆蓋基類方法來達到多態(tài)的目的。Liskov 替換原則的作用就是為了保證繼承復(fù)用的可靠。
下面來舉個違反替換原則的特殊例子:
正方形與長方形的問題也是屬于“圓不是橢圓”這類問題。我們知道正方形是一個特殊的長方形,所以可以設(shè)計兩個類,正方形類繼承自長方形類。長方形類有兩個成員變量,分別表示長和寬,有個計算面積的成員函數(shù)。假如計算面積的方法是virtual的,這樣能實現(xiàn)多態(tài)。在先設(shè)定長和寬后再調(diào)用計算面積的方法。我們知道正方形是長和寬相等的,如果設(shè)定長和寬的時候不是一樣的,然后調(diào)用了正方形的面積計算公式,這樣肯定就錯了。你可能會問咋這么扯蛋啊,為啥把長和寬設(shè)成不一樣啊。很多設(shè)計思想和方法是一來為了方便,二來為了讓用戶少犯錯誤,就是不管你怎么使用都不會出錯,要出錯應(yīng)該是在編譯時出錯,放置運行時出錯。如果出現(xiàn)上面說的情況編譯器是沒法讓你知道出錯了的。
所以一個正方形類繼承自長方形類的設(shè)計是不好的(注意的一點是你違反了 Liskov 替換原則并不是說就寫的代碼就會出錯,只是說設(shè)計不太合理。實際上你這樣設(shè)計代碼沒準(zhǔn)可以正常的跑得很好呢,如果沒有出現(xiàn)一些特殊情況可能是一點 bug 也沒有,只不過設(shè)計不合理為導(dǎo)致一些安全隱患而已)。
4.依賴倒置原則(DIP: Dependecy Inversion Principle)
其核心思想是:依賴于抽象。具體而言就是高層模塊不依賴于底層模塊,二者都依賴于抽象;抽象不依賴于具體,具體依賴于抽象。依賴倒置原則是對傳統(tǒng)過程性設(shè)計方法的“倒轉(zhuǎn)”,是高層次模塊復(fù)用及其可維護性的有效規(guī)范。
依賴一定存在于類與類、模塊與模塊之間。類與類之間產(chǎn)生依賴時,依賴倒置原則的理解可以描述如下:依賴就是剛開始時具體細節(jié)間互相依賴,我們將實現(xiàn)的細節(jié)變成抽象類,降低類間耦合度。然后有了抽象類,繼承自它的實現(xiàn)類也要依賴它。那倒置兩字咋理解呢? 一般情況我們是先關(guān)注細節(jié),然后根據(jù)細節(jié)抽象出來一些概括的方法,所以按常理一般是抽象要依賴于細節(jié)的,而現(xiàn)在是是倒過來了,確定一個抽象類后,那些細節(jié)的實現(xiàn)得以抽象出來的方法為基準(zhǔn),變成了細節(jié)依賴于抽象了,不然你要繼承了一個抽象類,你不完全實現(xiàn)它的方法的話可不讓你實例化對象的啊。
當(dāng)兩個模塊之間存在緊密的耦合關(guān)系時,最好的方法就是分離接口和實現(xiàn):在依賴之間定義一個抽象的接口,供高層模塊調(diào)用,底層模塊實現(xiàn)接口的定義,從而有效控制耦合關(guān)系,達到依賴于抽象的設(shè)計目的。
依賴于抽象就是不對實現(xiàn)編程,而對接口編程。依賴于抽象是一個通用原則,而有些時候依賴于細節(jié)是在所難免的,我們需要根據(jù)具體情況在在抽象與具體之間進行取舍。
5.接口分離原則(ISP: Interface Segregation Principle)
該原則的核心思想是:使用多個小的專門的接口,而不要使用一個大的總接口。具體而言,接口應(yīng)該是內(nèi)聚的,應(yīng)該避免“胖”接口。一個類對另一個類的依賴應(yīng)該建立在最小的接口上,而不要強迫依賴不同的方法,這是一種接口污染。
其實簡單點的講與前面說的單一職責(zé)類似,這里的接口不是函數(shù)接口,而是一個類。C#中的有專門的接口interface,和類區(qū)分開來,而且C#中不像C++支持類的多繼承,只支持接口的多繼承,所以這里可以把接口理解成功能更小更特殊的類,一個接口可能就只要那么幾個很少的方法就OK了。
接口分離手段主要有以下兩種方式:
(1)利用委托分離接口;
(2)利用多重繼承分離接口。
6.小結(jié)
概括地講,面向?qū)ο笤O(shè)計原則仍然是面向?qū)ο笏枷氲捏w現(xiàn)。例如,
- 單一職責(zé)原則要求類只負(fù)責(zé)一件事情;
- 接口分離原則,讓客戶只關(guān)心他們所需的接口。單一職責(zé)原則與接口分離原都體現(xiàn)了內(nèi)聚的思想;
- 開放封閉原則,要求類不作修改而能夠擴展功能,體現(xiàn)了類的封裝與繼承;
- Liskov 替換原則,要求派生類要能夠替換基類,是對類繼承的規(guī)范;
- 依賴倒置原則,要求類依賴于抽象,而不是實現(xiàn),是抽象思想的體現(xiàn)。
上面五條面向?qū)ο笤O(shè)計原則,可以幫助我們設(shè)計出代碼易復(fù)用、功能易擴展、維護易進行的程序,需要我們在實踐中遵守。
參考文獻
類設(shè)計的5個基本原則
李健.編寫高質(zhì)量代碼:改善C++程序的150個建議[M].第一版.北京:機械工業(yè)出版社,2012.1:316-317
總結(jié)
以上是生活随笔為你收集整理的面向对象设计的五项基本原则的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows 8 阅读器(Wi
- 下一篇: 可重定位目标文件(REL)的符号和符号表