若是您的设计依赖于继承,则须要找到一种方法来更改对象的类型以更改其行为。对于组合,您只须要更改对象使用的策略小程序
想象一下,咱们的经理忽然变成了按小时计酬的临时雇员。您能够经过如下方式在程序执行期间修改对象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参数