让我们首先考虑正方形和长方形。如果我们认为在接口方面,忽略了实现细节,方块是否是矩形的子类型?

子类型的定义取决于Liskov代换原理。为了成为一个子类型,它必须能够完成超级类型所做的一切。

如何定义矩形的接口?

zope.interface import Interface
class IRectangleInterface:
get_length:
"""Squares can do that"""
get_width:
"""Squares can do that"""
set_dimensions length width:
"""Uh oh"""

如果这是定义,则方块不能是矩形的子类型;它们不能响应set_dimensions方法,如果长度和宽度不同。

另一种方法是选择制作矩形。不变.

class IRectangleInterface:
get_length:
"""Squares can do that"""
get_width:
"""Squares can do that"""
with_dimensions length width:
"""Returns a new rectangle"""

现在,一个正方形可以是一个矩形。它可以返回一个新的矩形(通常不是正方形)with_dimensions被称为,但它不会停止成为一个正方形。

这似乎是一个学术问题-直到我们考虑到,从某种意义上说,正方形和长方形是它们两边的容器。在我们理解了这个例子之后,更实际的情况是使用更传统的容器。例如,考虑随机访问数组.

我们有ISquare和IRectangle,和ISquare是IRectangle.

我们希望在随机访问数组中放置矩形:

class IArrayOfRectanglesInterface:
get_element i:
"""Returns Rectangle"""
set_element i rectangle:
"""'rectangle' can be any IRectangle"""

我们也想把正方形放在一个随机存取数组中:

class IArrayOfSquareInterface:
get_element i:
"""Returns Square"""
set_element i square:
"""'square' can be any ISquare"""

即使ISquare是IRectangle,任何数组都不能实现这两者。IArrayOfSquare和IArrayOfRectangle.

为什么不行?假设bucket实现两者。

>>> rectangle make_rectangle
>>> bucket.set_element rectangle # This is allowed by IArrayOfRectangle
>>> thing bucket.get_element # That has to be a square by IArrayOfSquare
>>> assert thing.height thing.width
Traceback most recent call last:
File "" line module

AssertionError

两者都不能实现,这意味着两者都不是另一种类型的子类型,尽管ISquare是IRectangle。问题是set_element方法:如果我们有一个只读数组,IArrayOfSquare的子类型IArrayOfRectangle.

可变性,都是可变的。IRectangle接口和可变IArrayOf接口使得对类型和子类型的思考变得更加困难-而放弃的能力意味着我们期望类型之间的直观关系实际上仍然有效。

突变也可以非局部效果。当两个地方之间的共享对象被一个突变时,就会发生这种情况。典型的例子是一个线程与另一个线程交互一个共享对象,但是即使在一个单线程程序中,在相距很远的地方之间共享也很容易。考虑到在Python中,大多数对象都可以从许多地方访问:作为一个模块全局,或者在堆栈跟踪中,或者作为一个类属性。