面向對象的六大原則

  • 2020 年 3 月 15 日
  • 筆記

現在編程的主流語言基本上都是面向對象的。如C#,C++,JAVA。我們在使用時,已經構造了一個個的類。但是往往由於我們在類內部或外部的設計上存在種種問題,導致儘管是面向對象的語言,卻是面向過程的邏輯,甚至維護起來異常困難。每次增加或修改功能都要改動很多的程式碼,如履薄冰。而面向對象的六大原則主要的目的,就是我們如何設計類,更能很好的利用面向對象的特性

1)單一職責原則

       一個類永遠只有一個職責

  一套軟體就像是一個團隊,每個類就是團隊中的一個成員。團隊如果想穩定的發展。這些類就要各司其職,分工明確。如果類之間的功能出現了混淆,那麼軟體的整體結構就會非常的混亂。就像管理學中的一句話,如果一個職責由每個員工負責,那麼這個職責就沒有員工在負責。 這個原則的概念非常簡單,也是非常基礎的。很多人儘管沒有學習過面向對象的思想,但是經常寫程式碼之後也會不自覺的遵守這個原則。

Ps:在遵循單一職責原則的時候,常常會遇到職責擴散的問題。什麼是職責擴散呢?這裡簡單說下,在日常生活中,我們在對職責分類時,發現很多平常不受重視的職責,但是這些職責又不能忽視。於是就依次累加,最後分起類來會無窮無盡(有興趣的讀者可以參考下長尾定理)。為了解決這種問題,我們就需要有一些類,他的職責比較綜合(類似於“其它”)。類似於一個幫助類。但是這個類又不能太複雜了,否則我們就應該考慮怎麼把這個類分離開來。究竟這個類的複雜程度到了什麼時候情況下,我們就應該拆分呢?這個需要程式設計師根據軟體自身的複雜情況來判斷,沒有一個統一的標準。

2) 里氏替換原則

       “Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”

       “繼承必須確保超類所擁有的性質在子類中仍然成立“

   這個原則主要是為了體現面向對象的“繼承”特徵來提出的。 它的主旨就是,能夠使用基類的地方,必然也能夠透明的使用其子類,並且保證不會出錯。為了保證這種透明的無差別的使用,子類在使用時不應該隨意的重寫父類已經定義好的非抽象的方法。因為這些非抽象方法,類似於某種職能或契約,當父類保持這種約定時,子類也應該遵循並保證該特性,而非修改該特性。 我們在寫程式碼的時候,如果一個參數設定的是基類(或介面、抽象類),那麼我們傳送子類進去,一樣可以正常使用。因為基類相對於父類,只是一個更豐富,更具體,更詳細的表現形式。而不應該出現,傳入父類運行某種方法沒有問題,可是傳入子類運行時就報錯了。這在日常生活中也可以理解,汽車作為父類,他下面有卡車、轎車。轎車下邊又有兩廂,三廂等不同的繼承。但是無論是哪種汽車(父類)的職能,對於他的子類(卡車或轎車)都應該具有相同的職能,而不是相反的職能。以至於子類的子類(本例中是兩廂轎車)也應該擁有汽車一致的功能。

  我們在寫程式碼中,很容易出現複寫了父類的方法後,父類的方法發生了改動,而未考慮到子類的方法也需要作出相應的改動,導致程式碼出現錯誤。 通俗一點,可以理解為子類是遺傳自父類的。他在各種職能上也應該一脈相承自父類。而不應該隨意改變。  

 

  Ps 為什麼要叫里氏替換原則呢?這是因為最早提出這個理論的人姓里Liskov。這是電腦中少有的以姓氏命名的東西。

 3)最少知道原則

      Only talk to your immediate friends。(已經第二次引用英文,是不是很厲害23333)

      永遠只和你的朋友交流。

  我們在學習編程的初期,都會有人告訴我們要遵循“高內聚,低耦合”。而OO中也將“封裝”作為對象的基本特徵之一。最少知道原則其實體現的就是“高內聚,低耦合”這句話。

(1)低耦合:一個類對於自己依賴的類,知道的越少越好。不要讓一個類依賴過多的類。否則這個類很容受外界的影響,並且因為這種影響要改變自身的程式碼(自身要適應)。

(2)高內聚:將實現邏輯都封裝在類的內部,對public方法以外的資訊,不輕易暴露給外界。這是由於public對外後,相當於是一種契約,一種許諾。你要再後邊的實現中,不斷的去兼容這種public,以防止調用它的程式碼不會報錯。 上面這樣說,可能有點抽象,這裡舉個例子。在很多人對另一方的要求,都有一條,社會關係不要複雜。為什麼會這樣呢?因為一個人如果他和外界的關係越複雜,他就越不穩定,不怕人找事,就怕事找人。或許他的本性是好的,但是周邊的龍魚混雜,三天兩頭的總會有事。避免這種問題的最好辦法,就是一開始就做一個安靜的美男子。  

 

4)介面隔離原則

       一個類對於另外一個類的依賴應該建立在最小的介面上。

   一個介面定義的過於臃腫,則代表他的每一個實現類都要考慮所有的實現邏輯。如果一個類實現了某個介面,也就是說這個類承載了這個介面所有的功能,維護這些功能成為了自己的職責。這就無形中增加了一個類的負擔。

這裡有兩點需要說明一下:

(1)介面定義的小,但是要有限度。對介面細化可以增加靈活性,但是過度細化則會使設計複雜化。同時介面的使用率不高,提高了程式碼的維護成本。這種極端的體現就是每個介面只含有一個方法,這顯然是不合適的。

(2)介面隔離原則和單一原則的區別

共同點:都是儘可能的縮小涉及的範圍。

不同點:單一原則主要是指封裝性。他針對的是一個類、一個方法,是從對象(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )的角度考慮的。而介面隔離原則是指類之間的耦合應該保持的一個度。他針對的是類(對象)和類(對象)之間的關係。如果說單一原則指的是思想單純,那麼介面隔離指的就是社會關係簡單啦。

5)依賴置換原則

     這個原則的名字比較唬人,我們先看看他的內容究竟是什麼。在設計模式中對該原則有兩句經典的描述:

   (1)高層模組不應該依賴底層模組。兩者都應該依賴抽象。

   (2)抽象不應該依賴細節,細節應該依賴抽象。

  這兩句話的含義是:高層模組不應該依賴底層模組。兩者應該通過抽象的東西進行關係鏈接(抽象的東西是指介面或者抽象類)。其次抽象類或者一個介面不應該依賴某個實現類。而這些實現類反而應該依賴於這個抽象類的設定。

通俗一點的說法就是,模組之間不應該直接產生調用關係(這是舊有的調用關係),兩者應該通過面向介面(或者理解為面向設定的契約)進行編程。而這些契約和介面更不應該以來自底層模組而設定。這些底層模組反而應該遵守這些契約。因為契約(抽象類、介面)相對於哪些實現程式碼,更不會改變,也就是更穩定。所以依賴置換原則又叫作面向介面編程或面向契約編程。本意就是調整原來的依賴關係,重行進行了設定。  

6)開閉原則

     開閉原則是指:一個軟體、一套系統在開發完成後,當有增加或修改需求時,應該對拓展程式碼打開,對修改原有程式碼關閉。

  類一旦確定,就不應該再對其功能發生修改。這是面向對象設計中,最重要最核心的原則。方案發布後,我們最擔心的是什麼?就是需求的變化,而需求一旦有變化,就要修改程式碼。大部分的bug往往就是這時候引入的。因為修改程式碼時,我們往往將重點放在,如何解決當前bug上,反而沒有注意因為這個修改,對原有設計的影響。

     這條原則沒有具體的指導要求,是前邊五條原則的根本。

 

ps,這六個面向對象的原則,並不是是和否的問題,也不是遵守和不遵守的問題。而是遵守的多和遵守的少的問題。我在文中也多次強調,我們在設計時,應該注意把握一個度。誠然儘可能的遵守這些原則,會使程式碼維護起來更容易。但是維護粒度過細,所需要的設計和開發成本成倍增加,這顯然是捨本逐末的。如圖,面向對象開發原則可以從以下這個坐標圖展示,不論是哪個維度,他的值都不應該過滿,甚至溢出,當然也不能很低,保持一個適當的度即可。

 

 

 

作者署名jilodream/王若伊_恩賜解脫(部落格鏈接:http://www.cnblogs.com/jilodream/)

現在編程的主流語言基本上都是面向對象的。如C#,C++,JAVA。我們在使用時,已經構造了一個個的類。但是往往由於我們在類內部或外部的設計上存在種種問題,導致儘管是面向對象的語言,卻是面向過程的邏輯,甚至維護起來異常困難。每次增加或修改功能都要改動很多的程式碼,如履薄冰。而面向對象的六大原則主要的目的,就是我們如何設計類,更能很好的利用面向對象的特性

1)單一職責原則

       一個類永遠只有一個職責

  一套軟體就像是一個團隊,每個類就是團隊中的一個成員。團隊如果想穩定的發展。這些類就要各司其職,分工明確。如果類之間的功能出現了混淆,那麼軟體的整體結構就會非常的混亂。就像管理學中的一句話,如果一個職責由每個員工負責,那麼這個職責就沒有員工在負責。 這個原則的概念非常簡單,也是非常基礎的。很多人儘管沒有學習過面向對象的思想,但是經常寫程式碼之後也會不自覺的遵守這個原則。

Ps:在遵循單一職責原則的時候,常常會遇到職責擴散的問題。什麼是職責擴散呢?這裡簡單說下,在日常生活中,我們在對職責分類時,發現很多平常不受重視的職責,但是這些職責又不能忽視。於是就依次累加,最後分起類來會無窮無盡(有興趣的讀者可以參考下長尾定理)。為了解決這種問題,我們就需要有一些類,他的職責比較綜合(類似於“其它”)。類似於一個幫助類。但是這個類又不能太複雜了,否則我們就應該考慮怎麼把這個類分離開來。究竟這個類的複雜程度到了什麼時候情況下,我們就應該拆分呢?這個需要程式設計師根據軟體自身的複雜情況來判斷,沒有一個統一的標準。

2) 里氏替換原則

       “Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”

       “繼承必須確保超類所擁有的性質在子類中仍然成立“

   這個原則主要是為了體現面向對象的“繼承”特徵來提出的。 它的主旨就是,能夠使用基類的地方,必然也能夠透明的使用其子類,並且保證不會出錯。為了保證這種透明的無差別的使用,子類在使用時不應該隨意的重寫父類已經定義好的非抽象的方法。因為這些非抽象方法,類似於某種職能或契約,當父類保持這種約定時,子類也應該遵循並保證該特性,而非修改該特性。 我們在寫程式碼的時候,如果一個參數設定的是基類(或介面、抽象類),那麼我們傳送子類進去,一樣可以正常使用。因為基類相對於父類,只是一個更豐富,更具體,更詳細的表現形式。而不應該出現,傳入父類運行某種方法沒有問題,可是傳入子類運行時就報錯了。這在日常生活中也可以理解,汽車作為父類,他下面有卡車、轎車。轎車下邊又有兩廂,三廂等不同的繼承。但是無論是哪種汽車(父類)的職能,對於他的子類(卡車或轎車)都應該具有相同的職能,而不是相反的職能。以至於子類的子類(本例中是兩廂轎車)也應該擁有汽車一致的功能。

  我們在寫程式碼中,很容易出現複寫了父類的方法後,父類的方法發生了改動,而未考慮到子類的方法也需要作出相應的改動,導致程式碼出現錯誤。 通俗一點,可以理解為子類是遺傳自父類的。他在各種職能上也應該一脈相承自父類。而不應該隨意改變。  

 

  Ps 為什麼要叫里氏替換原則呢?這是因為最早提出這個理論的人姓里Liskov。這是電腦中少有的以姓氏命名的東西。

 3)最少知道原則

      Only talk to your immediate friends。(已經第二次引用英文,是不是很厲害23333)

      永遠只和你的朋友交流。

  我們在學習編程的初期,都會有人告訴我們要遵循“高內聚,低耦合”。而OO中也將“封裝”作為對象的基本特徵之一。最少知道原則其實體現的就是“高內聚,低耦合”這句話。

(1)低耦合:一個類對於自己依賴的類,知道的越少越好。不要讓一個類依賴過多的類。否則這個類很容受外界的影響,並且因為這種影響要改變自身的程式碼(自身要適應)。

(2)高內聚:將實現邏輯都封裝在類的內部,對public方法以外的資訊,不輕易暴露給外界。這是由於public對外後,相當於是一種契約,一種許諾。你要再後邊的實現中,不斷的去兼容這種public,以防止調用它的程式碼不會報錯。 上面這樣說,可能有點抽象,這裡舉個例子。在很多人對另一方的要求,都有一條,社會關係不要複雜。為什麼會這樣呢?因為一個人如果他和外界的關係越複雜,他就越不穩定,不怕人找事,就怕事找人。或許他的本性是好的,但是周邊的龍魚混雜,三天兩頭的總會有事。避免這種問題的最好辦法,就是一開始就做一個安靜的美男子。  

 

4)介面隔離原則

       一個類對於另外一個類的依賴應該建立在最小的介面上。

   一個介面定義的過於臃腫,則代表他的每一個實現類都要考慮所有的實現邏輯。如果一個類實現了某個介面,也就是說這個類承載了這個介面所有的功能,維護這些功能成為了自己的職責。這就無形中增加了一個類的負擔。

這裡有兩點需要說明一下:

(1)介面定義的小,但是要有限度。對介面細化可以增加靈活性,但是過度細化則會使設計複雜化。同時介面的使用率不高,提高了程式碼的維護成本。這種極端的體現就是每個介面只含有一個方法,這顯然是不合適的。

(2)介面隔離原則和單一原則的區別

共同點:都是儘可能的縮小涉及的範圍。

不同點:單一原則主要是指封裝性。他針對的是一個類、一個方法,是從對象(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )的角度考慮的。而介面隔離原則是指類之間的耦合應該保持的一個度。他針對的是類(對象)和類(對象)之間的關係。如果說單一原則指的是思想單純,那麼介面隔離指的就是社會關係簡單啦。

5)依賴置換原則

     這個原則的名字比較唬人,我們先看看他的內容究竟是什麼。在設計模式中對該原則有兩句經典的描述:

   (1)高層模組不應該依賴底層模組。兩者都應該依賴抽象。

   (2)抽象不應該依賴細節,細節應該依賴抽象。

  這兩句話的含義是:高層模組不應該依賴底層模組。兩者應該通過抽象的東西進行關係鏈接(抽象的東西是指介面或者抽象類)。其次抽象類或者一個介面不應該依賴某個實現類。而這些實現類反而應該依賴於這個抽象類的設定。

通俗一點的說法就是,模組之間不應該直接產生調用關係(這是舊有的調用關係),兩者應該通過面向介面(或者理解為面向設定的契約)進行編程。而這些契約和介面更不應該以來自底層模組而設定。這些底層模組反而應該遵守這些契約。因為契約(抽象類、介面)相對於哪些實現程式碼,更不會改變,也就是更穩定。所以依賴置換原則又叫作面向介面編程或面向契約編程。本意就是調整原來的依賴關係,重行進行了設定。  

6)開閉原則

     開閉原則是指:一個軟體、一套系統在開發完成後,當有增加或修改需求時,應該對拓展程式碼打開,對修改原有程式碼關閉。

  類一旦確定,就不應該再對其功能發生修改。這是面向對象設計中,最重要最核心的原則。方案發布後,我們最擔心的是什麼?就是需求的變化,而需求一旦有變化,就要修改程式碼。大部分的bug往往就是這時候引入的。因為修改程式碼時,我們往往將重點放在,如何解決當前bug上,反而沒有注意因為這個修改,對原有設計的影響。

     這條原則沒有具體的指導要求,是前邊五條原則的根本。

 

ps,這六個面向對象的原則,並不是是和否的問題,也不是遵守和不遵守的問題。而是遵守的多和遵守的少的問題。我在文中也多次強調,我們在設計時,應該注意把握一個度。誠然儘可能的遵守這些原則,會使程式碼維護起來更容易。但是維護粒度過細,所需要的設計和開發成本成倍增加,這顯然是捨本逐末的。如圖,面向對象開發原則可以從以下這個坐標圖展示,不論是哪個維度,他的值都不應該過滿,甚至溢出,當然也不能很低,保持一個適當的度即可。