面向對象的幾大原則2

一、接口隔離原則

接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。

根據(jù)接口隔離原則,當一個接口太大時,我們需要將它分割成一些更細小的接口,使用該接口的客戶端僅需知道與之相關的方法即可。每一個接口應該承擔一種相對獨立的角色,不干不該干的事,該干的事都要干。這里的“接口”往往有兩種不同的含義:一種是指一個類型所具有的方法特征的集合,僅僅是一種邏輯上的抽象;另外一種是指某種語言具體的“接口”定義,有嚴格的定義和結構,比如Java語言中的interface。對于這兩種不同的含義,ISP的表達方式以及含義都有所不同:

(1) 當把“接口”理解成一個類型所提供的所有方法特征的集合的時候,這就是一種邏輯上的概念,接口的劃分將直接帶來類型的劃分??梢园呀涌诶斫獬山巧?,一個接口只能代表一個角色,每個角色都有它特定的一個接口,此時,這個原則可以叫做“角色隔離原則”。
第二種比較容易懂一些,主要談第二種,就是真正的interface接口
(2) 如果把“接口”理解成狹義的特定語言的接口,那么ISP表達的意思是指接口僅僅提供客戶端需要的行為,客戶端不需要的行為則隱藏起來,應當為客戶端提供盡可能小的單獨的接口,而不要提供大的總接口。在面向對象編程語言中,實現(xiàn)一個接口就需要實現(xiàn)該接口中定義的所有方法,因此大的總接口使用起來不一定很方便,為了使接口的職責單一,需要將大接口中的方法根據(jù)其職責不同分別放在不同的小接口中,以確保每個接口使用起來都較為方便,并都承擔某一單一角色。接口應該盡量細化,同時接口中的方法應該盡量少,每個接口中只包含一個客戶端(如子模塊或業(yè)務邏輯類)所需的方法即可,這種機制也稱為“定制服務”,即為不同的客戶端提供寬窄不同的接口。

下面通過一個簡單實例來加深對接口隔離原則的理解:

Sunny軟件公司開發(fā)人員針對某CRM系統(tǒng)的客戶數(shù)據(jù)顯示模塊設計了如圖1所示接口,其中方法dataRead()用于從文件中讀取數(shù)據(jù),方法transformToXML()用于將數(shù)據(jù)轉換成XML格式,方法createChart()用于創(chuàng)建圖表,方法displayChart()用于顯示圖表,方法createReport()用于創(chuàng)建文字報表,方法displayReport()用于顯示文字報表。

圖1 初始設計方案結構圖

在實際使用過程中發(fā)現(xiàn)該接口很不靈活,例如如果一個具體的數(shù)據(jù)顯示類無須進行數(shù)據(jù)轉換(源文件本身就是XML格式),但由于實現(xiàn)了該接口,將不得不實現(xiàn)其中聲明的transformToXML()方法(至少需要提供一個空實現(xiàn));如果需要創(chuàng)建和顯示圖表,除了需實現(xiàn)與圖表相關的方法外,還需要實現(xiàn)創(chuàng)建和顯示文字報表的方法,否則程序編譯時將報錯。

現(xiàn)使用接口隔離原則對其進行重構。
在圖1中,由于在接口CustomerDataDisplay中定義了太多方法,即該接口承擔了太多職責,一方面導致該接口的實現(xiàn)類很龐大,在不同的實現(xiàn)類中都不得不實現(xiàn)接口中定義的所有方法,靈活性較差,如果出現(xiàn)大量的空方法,將導致系統(tǒng)中產生大量的無用代碼,影響代碼質量;另一方面由于客戶端針對大接口編程,將在一定程序上破壞程序的封裝性,客戶端看到了不應該看到的方法,沒有為客戶端定制接口。因此需要將該接口按照接口隔離原則和單一職責原則進行重構,將其中的一些方法封裝在不同的小接口中,確保每一個接口使用起來都較為方便,并都承擔某一單一角色,每個接口中只包含一個客戶端(如模塊或類)所需的方法即可。
通過使用接口隔離原則,本實例重構后的結構如圖2所示:

圖2 重構后的結構圖

在使用接口隔離原則時,我們需要注意控制接口的粒度,接口不能太小,如果太小會導致系統(tǒng)中接口泛濫,不利于維護;接口也不能太大,太大的接口將違背接口隔離原則,靈活性較差,使用起來很不方便。一般而言,接口中僅包含為某一類用戶定制的方法即可,不應該強迫客戶依賴于那些它們不用的方法。

二、合成復用原則

合成復用原則又稱為組合/聚合復用原則(Composition/Aggregate Reuse Principle, CARP),其定義如下:

合成復用原則(Composite Reuse Principle, CRP):盡量使用對象組合,而不是繼承來達到復用的目的。

合成復用原則就是在一個新的對象里通過關聯(lián)關系(包括組合關系和聚合關系)來使用一些已有的對象,使之成為新對象的一部分;新對象通過委派調用已有對象的方法達到復用功能的目的。簡言之:復用時要盡量使用組合/聚合關系(關聯(lián)關系),少用繼承。
以下可以不看,因為是對上的詳細闡述
在面向對象設計中,可以通過兩種方法在不同的環(huán)境中復用已有的設計和實現(xiàn),即通過組合/聚合關系或通過繼承,但首先應該考慮使用組合/聚合,組合/聚合可以使系統(tǒng)更加靈活,降低類與類之間的耦合度,一個類的變化對其他類造成的影響相對較少;其次才考慮繼承,在使用繼承時,需要嚴格遵循里氏代換原則,有效使用繼承會有助于對問題的理解,降低復雜度,而濫用繼承反而會增加系統(tǒng)構建和維護的難度以及系統(tǒng)的復雜度,因此需要慎重使用繼承復用。
通過繼承來進行復用的主要問題在于繼承復用會破壞系統(tǒng)的封裝性,因為繼承會將基類的實現(xiàn)細節(jié)暴露給子類,由于基類的內部細節(jié)通常對子類來說是可見的,所以這種復用又稱“白箱”復用,如果基類發(fā)生改變,那么子類的實現(xiàn)也不得不發(fā)生改變;從基類繼承而來的實現(xiàn)是靜態(tài)的,不可能在運行時發(fā)生改變,沒有足夠的靈活性;而且繼承只能在有限的環(huán)境中使用(如類沒有聲明為不能被繼承)。
由于組合或聚合關系可以將已有的對象(也可稱為成員對象)納入到新對象中,使之成為新對象的一部分,因此新對象可以調用已有對象的功能,這樣做可以使得成員對象的內部實現(xiàn)細節(jié)對于新對象不可見,所以這種復用又稱為“黑箱”復用,相對繼承關系而言,其耦合度相對較低,成員對象的變化對新對象的影響不大,可以在新對象中根據(jù)實際需要有選擇性地調用成員對象的操作;合成復用可以在運行時動態(tài)進行,新對象可以動態(tài)地引用與成員對象類型相同的其他對象。

一般而言,如果兩個類之間是“Has-A”的關系應使用組合或聚合,如果是“Is-A”關系可使用繼承。"Is-A"是嚴格的分類學意義上的定義,意思是一個類是另一個類的"一種";而"Has-A"則不同,它表示某一個角色具有某一項責任。【我的理解,這個是一個到底用哪種方式復用的一個判斷方法】

下面通過一個簡單實例來加深對合成復用原則的理解:

Sunny軟件公司開發(fā)人員在初期的CRM系統(tǒng)設計中,考慮到客戶數(shù)量不多,系統(tǒng)采用MySQL作為數(shù)據(jù)庫,與數(shù)據(jù)庫操作有關的類如CustomerDAO類等都需要連接數(shù)據(jù)庫,連接數(shù)據(jù)庫的方法getConnection()封裝在DBUtil類中,由于需要重用DBUtil類的getConnection()方法,設計人員將CustomerDAO作為DBUtil類的子類,初始設計方案結構如圖1所示:

圖1 初始設計方案結構圖

隨著客戶數(shù)量的增加,系統(tǒng)決定升級為Oracle數(shù)據(jù)庫,因此需要增加一個新的OracleDBUtil類來連接Oracle數(shù)據(jù)庫,由于在初始設計方案中CustomerDAO和DBUtil之間是繼承關系,因此在更換數(shù)據(jù)庫連接方式時需要修改CustomerDAO類的源代碼,將CustomerDAO作為OracleDBUtil的子類,這將違反開閉原則。【當然也可以修改DBUtil類的源代碼,同樣會違反開閉原則?!?br> 根據(jù)合成復用原則,我們在實現(xiàn)復用時應該多用關聯(lián),少用繼承。因此在本實例中我們可以使用關聯(lián)復用來取代繼承復用,重構后的結構如圖2所示:
圖2 重構后的結構圖

在圖2中,CustomerDAO和DBUtil之間的關系由繼承關系變?yōu)殛P聯(lián)關系,采用依賴注入的方式將DBUtil對象注入到CustomerDAO中,可以使用構造注入,也可以使用Setter注入。如果需要對DBUtil的功能進行擴展,可以通過其子類來實現(xiàn),如通過子類OracleDBUtil來連接Oracle數(shù)據(jù)庫。由于CustomerDAO針對DBUtil編程(這個是前提,所以面向抽象類編程多么的重要),根據(jù)里氏代換原則,DBUtil子類的對象可以覆蓋DBUtil對象,只需在CustomerDAO中注入子類對象即可使用子類所擴展的方法。例如在CustomerDAO中注入OracleDBUtil對象,即可實現(xiàn)Oracle數(shù)據(jù)庫連接,原有代碼無須進行修改,而且還可以很靈活地增加新的數(shù)據(jù)庫連接方式。

三、迪米特法則

迪米特法則(Law of Demeter, LoD):一個軟件實體應當盡可能少地與其他實體發(fā)生相互作用。

迪米特法則可降低系統(tǒng)的耦合度,使類與類之間保持松散的耦合關系。
迪米特法則還有幾種定義形式,包括:不要和“陌生人”說話、只與你的直接朋友通信等,在迪米特法則中,對于一個對象,其朋友包括以下幾類:

  (1) 當前對象本身(this);

  (2) 以參數(shù)形式傳入到當前對象方法中的對象;

  (3) 當前對象的成員對象;

  (4) 如果當前對象的成員對象是一個集合,那么集合中的元素也都是朋友;

  (5) 當前對象所創(chuàng)建的對象。

任何一個對象,如果滿足上面的條件之一,就是當前對象的“朋友”,否則就是“陌生人”。在應用迪米特法則時,一個對象只能與直接朋友發(fā)生交互,不要與“陌生人”發(fā)生直接交互,這樣做可以降低系統(tǒng)的耦合度,一個對象的改變不會給太多其他對象帶來影響。

迪米特法則要求我們在設計系統(tǒng)時,應該盡量減少對象之間的交互,如果兩個對象之間不必彼此直接通信,那么這兩個對象就不應當發(fā)生任何直接的相互作用,簡言之,就是通過引入一個合理的第三者來降低現(xiàn)有對象之間的耦合度。

下面通過一個簡單實例來加深對迪米特法則的理解:

Sunny軟件公司所開發(fā)CRM系統(tǒng)包含很多業(yè)務操作窗口,在這些窗口中,某些界面控件之間存在復雜的交互關系,一個控件事件的觸發(fā)將導致多個其他界面控件產生響應,例如,當一個按鈕(Button)被單擊時,對應的列表框(List)、組合框(ComboBox)、文本框(TextBox)、文本標簽(Label)等都將發(fā)生改變,在初始設計方案中,界面控件之間的交互關系可簡化為如圖1所示結構:


圖1 初始設計方案結構圖

在圖1中,由于界面控件之間的交互關系復雜,導致在該窗口中增加新的界面控件時需要修改與之交互的其他控件的源代碼,系統(tǒng)擴展性較差,也不便于增加和刪除新控件。

現(xiàn)使用迪米特對其進行重構。

在本實例中,可以通過引入一個專門用于控制界面控件交互的中間類(Mediator)來降低界面控件之間的耦合度。引入中間類之后,界面控件之間不再發(fā)生直接引用,而是將請求先轉發(fā)給中間類,再由中間類來完成對其他控件的調用。當需要增加或刪除新的控件時,只需修改中間類即可,無須修改新增控件或已有控件的源代碼,重構后結構如圖2所示:


圖2 重構后的結構圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容