如何用子类型来思考多态性

时间:2014-07-25 09:28:14

标签: oop types polymorphism theory liskov-substitution-principle

Liskov替代原则声明:

  

超类型的不变量必须保留在子类型中。

我对这个原则和多态性的交集特别感兴趣。事实上,特别是亚型多态性,参数多态和Haskell类型似乎就是这种情况。

所以,我知道当函数的参数是逆变的并且它们的返回类型是协变的时,函数是子类型。我们可以假设方法只是具有隐式" self"论点。但是,这似乎暗示如果子类重写父类的方法,则它不再是子类型,因为它的一个方法不再是子类型。

例如。请使用以下伪代码:

class Parent:
    count : int
    increment : Parent -> ()
    {
        count += 1
    }

class Child inherits Parent:
    increment : Child -> ()
    {
        count += 2
    }

回到LSP:我们可以说Parent.increment()的属性应该适用于Child.increment(),即使这两个属性不遵守严格的子类型关系吗?

更一般地说,我的问题是:子类型的规则如何与多态函数的更具体的参数接口,以及正确思考这两个概念的正确方法是什么?

2 个答案:

答案 0 :(得分:0)

引用维基百科关于Liskov Substitution Principle

的文章
  

更正式地说,Liskov替代原则(LSP)是一个特殊的   定义子类型关系,称为(强)行为   子类型 [...]

     

行为子类型比典型的亚型更强的概念   类型理论中定义的函数,它仅依赖于   参数类型的逆变和返回类型的协方差。   一般来说,行为子类型通常是不可判定的[...]

     

子类型必须具有许多行为条件   满足:

     
      
  • 无法在子类型中加强先决条件。
  •   
  • 后置条件不能在子类型中被削弱。
  •   
  • 超类型的不变量必须保留在子类型中。
  •   

因此,LSP是一种更强的子类型定义,它依赖于类型理论之外的特征。

在你的例子中,这会上升并落在你的不变量上。

calling increment will increase count by **exactly 1**

显然 Child 无法用 Parent 表示,因为不变量已被破坏。这不能仅从语法中推断出来。

LSP应该引导您单独定义父级和子级,让它们都从Incrementable继承,后者条件较弱。

答案 1 :(得分:0)

术语“子类型”在技术上是一个句法问题。所以语法上,Child <: Parent

Liskov原则是关于行为子类型,如维基百科所述。它需要语法子类型,但它还取决于你对类的不变性和前/后条件的定义。由于您没有定义任何内容,因此谈论违规行为是毫无意义的。

如果您将increment的后置条件定义为new count = old count + 1,则存在违规行为。

如果您将increment的后置条件定义为new count > old count,则没有。{/ p>

通常,将后置条件定义为“完全是父级的后置条件”会使包含多态性无法通过定义。在多态有意义的地方,应该放宽后置条件的定义。

请注意,类不变量是关于可能的值 - 对象的快照 - 并且因为您可以根据Parent increment定义Child increment,所以它不能违反任何不变量。