若是您的設計依賴於繼承,則須要找到一種方法來更改對象的類型以更改其行爲。對於組合,您只須要更改對象使用的策略小程序
想象一下,咱們的經理忽然變成了按小時計酬的臨時僱員。您能夠經過如下方式在程序執行期間修改對象app
, () () () . [] . () .(, ) .()
該程序從EmployeeDatabase
獲取員工列表,並檢索第一個員工,即咱們想要的經理。而後,它會建立一個新的HourlyPolicy
,初始化爲每小時$55
,並將其分配給manager
對象ide
$ . : . : . : . : . : . : : : . , : : : , : : : . , : : : . , : : : . ,
到目前爲止,您已經瞭解了在Python中繼承和組合是如何工做的。您已經看到派生類繼承了它們的基類的接口和實現。您還看到了組合容許您重用另外一個類的實現測試
對於同一個問題,您已經實現了兩個解決方案。第一個解決方案使用多重繼承,第二個使用複合url
您還看到Python的duck類型化容許您經過實現所需的接口來重用具備程序現有部分的對象。在Python中,沒有必要從基類派生出要重用的類spa
此時,您可能會問何時在Python中使用繼承與組合。它們都支持代碼重用。繼承和組合能夠解決Python程序中的相似問題設計
通常的建議是使用在兩個類之間建立較少依賴關係的關係。這種關係就是組成。不過,有時繼承會更有意義。code
如下部分提供了一些指導原則,幫助您在Python中的繼承和組合之間作出正確的選擇orm
繼承僅應用於爲一個關係建模。Liskov的替換原理說,繼承自Base的Derived類型的對象能夠替換Base類型的對象,而無需更改程序的所需屬性對象
Liskov的替代原則是決定繼承是不是合適的設計解決方案的最重要的指導原則。不過,答案可能並不是在全部狀況下都是直截了當的。幸運的是,您可使用一個簡單的測試來肯定您的設計是否遵循Liskov的替換原則
假設您有一個類a,它提供了一個您但願在另外一個類B中重用的實現和接口。您最初的想法是能夠從a派生出B,並繼承接口和實現。爲了確保這是正確的設計,您須要遵循如下步驟:
您有一個類矩形,它公開一個.area屬性。您須要一個類Square,它也有一個.area。彷佛正方形是一種特殊類型的矩形,因此您能夠從它派生並利用接口和實現。
正方形是長方形,由於它的面積是由它的高乘以它的長計算出來的。約束條件是這個平方。高度和廣場。長度必須相等。
它是有意義的。你能夠證實這種關係,並解釋爲何正方形是長方形。讓咱們來顛倒一下這種關係,看看它是否有意義
長方形是正方形,由於它的面積是由它的高乘以它的長計算出來的。差值就是這個矩形。高度和矩形。寬度能夠獨立變化
: (, , ): . . (): . .
使用長度和高度初始化Rectangle
類,它提供一個.area
屬性來返回該區域。長度和高度被封裝,以免直接改變它們。
(): (, ): ().(, )
Square
類使用side_size
初始化,該side_size
用於初始化基類的兩個組件。如今,您編寫一個小程序來測試行爲
(, ) . () . ()
運行程序
$ . !
: (, , ): . . (): . . (, , ): . .
.resize()
採用對象的new_length
和new_width
。您能夠將如下代碼添加到程序中,以驗證其是否正常運行
.(, ) . ()
您調整矩形對象的大小,並斷言新區域正確。您能夠運行該程序以驗證行爲
$ . !
那麼,若是調整正方形大小會怎樣?修改程序,而後嘗試修改正方形對象
.(, ) ()
將與矩形相同的參數傳遞給square.resize(),而後打印該區域。當你運行程序時,你會看到
$ . : !
程序顯示,新的區域是15像矩形對象。如今的問題是,square對象再也不知足其長度和高度必須相等的square類約束
你怎麼解決這個問題?你能夠嘗試幾種方法,但全部的方法都會很尷尬。您能夠在square中覆蓋.resize()並忽略height參數,可是這對於查看程序的其餘部分的人來講會很混亂,由於這些部分的矩形正在被調整大小,而其中一些矩形並無獲得預期的區域,由於它們其實是正方形。
在一個像這樣的小程序中,可能很容易發現奇怪行爲的緣由,可是在一個更復雜的程序中,問題就更難找到了
事實是,若是可以以兩種方式證實兩個類之間的繼承關係,就不該該從另外一個類派生出另外一個類
在本例中,Square
從Rectangle
繼承.resize()
的接口和實現是沒有意義的。這並不意味着方形對象不能調整大小。這意味着接口是不一樣的,由於它只須要一個side_size參數