Java設計原則之里氏替換原則


里氏代換原則由2008年圖靈獎得主、美國第一位計算機科學女博士Barbara Liskov教授和卡內基·梅隆大學Jeannette Wing教授於1994年提出。其嚴格表述如下:如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程序P在所有的對象o1代換o2時,程序P的行為沒有變化,那么類型S是類型T的子類型。這個定義比較拗口且難以理解,因此我們一般使用它的另一個通俗版定義:

里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對象。

      里氏代換原則告訴我們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那么它不一定能夠使用基類對象。例如:我喜歡動物,那我一定喜歡狗,因為狗是動物的子類;但是我喜歡狗,不能據此斷定我喜歡動物,因為我並不喜歡老鼠,雖然它也是動物。

      例如有兩個類,一個類為BaseClass,另一個是SubClass類,並且SubClass類是BaseClass類的子類,那么一個方法如果可以接受一個BaseClass類型的基類對象base的話,如:method1(base),那么它必然可以接受一個BaseClass類型的子類對象sub,method1(sub)能夠正常運行。反過來的代換不成立,如一個方法method2接受BaseClass類型的子類對象sub為參數:method2(sub),那么一般而言不可以有method2(base),除非是重載方法。

      里氏代換原則是實現開閉原則的重要方式之一,由於使用基類對象的地方都可以使用子類對象,因此在程序中盡量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象。

      在使用里氏代換原則時需要注意如下幾個問題:

      (1)子類的所有方法必須在父類中聲明,或子類必須實現父類中聲明的所有方法。根據里氏代換原則,為了保證系統的擴展性,在程序中通常使用父類來進行定義,如果一個方法只存在子類中,在父類中不提供相應的聲明,則無法在以父類定義的對象中使用該方法。

      (2)  我們在運用里氏代換原則時,盡量把父類設計為抽象類或者接口,讓子類繼承父類或實現父接口,並實現在父類中聲明的方法,運行時,子類實例替換父類實例,我們可以很方便地擴展系統的功能,同時無須修改原有子類的代碼,增加新的功能可以通過增加一個新的子類來實現。里氏代換原則是開閉原則的具體實現手段之一。

      (3) Java語言中,在編譯階段,Java編譯器會檢查一個程序是否符合里氏代換原則,這是一個與實現無關的、純語法意義上的檢查,但Java編譯器的檢查是有局限的。

      在Sunny軟件公司開發的CRM系統中,客戶(Customer)可以分為VIP客戶(VIPCustomer)和普通客戶(CommonCustomer)兩類,系統需要提供一個發送Email的功能,原始設計方案如圖1所示:

圖1原始結構圖

      在對系統進行進一步分析后發現,無論是普通客戶還是VIP客戶,發送郵件的過程都是相同的,也就是說兩個send()方法中的代碼重復,而且在本系統中還將增加新類型的客戶。為了讓系統具有更好的擴展性,同時減少代碼重復,使用里氏代換原則對其進行重構。

      在本實例中,可以考慮增加一個新的抽象客戶類Customer,而將CommonCustomer和VIPCustomer類作為其子類,郵件發送類EmailSender類針對抽象客戶類Customer編程,根據里氏代換原則,能夠接受基類對象的地方必然能夠接受子類對象,因此將EmailSender中的send()方法的參數類型改為Customer,如果需要增加新類型的客戶,只需將其作為Customer類的子類即可。重構后的結構如圖2所示:

圖2  重構后的結構圖

      里氏代換原則是實現開閉原則的重要方式之一。在本實例中,在傳遞參數時使用基類對象,除此以外,在定義成員變量、定義局部變量、確定方法返回類型時都可使用里氏代換原則。針對基類編程,在程序運行時再確定具體子類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM