子类和子类型之间有什么区别?


44

关于这个问题的最高的答案是关于Liskov替代原理的努力,竭力区分子类型子类。这也说明有些语言将两者混为一谈,而另一些则没有。

对于我最熟悉的面向对象语言(Python,C ++),“类型”和“类”是同义词。对于C ++,在子类型和子类之间进行区分意味着什么?举例来说,这Foo是的子类,但不是子类型FooBase。如果foo是的实例Foo,则此行:

FooBase* fbPoint = &foo;

不再有效?


6
实际上,在Python中,“类型”和“类”是不同的概念。事实上,巨蟒被动态类型,“类型”不是一个概念在Python。不幸的是,Python开发人员不理解这一点,并且仍然将两者混为一谈。
约尔格W¯¯米塔格

11
在C ++中,“类型”和“类”也是不同的。“整数数组”是一种类型;这是什么班?“指向类型为int的变量的指针”是一种类型;这是什么班?这些东西不是任何类,但是肯定是类型。
埃里克·利珀特

2
阅读该问题和答案后,我很想知道这件事。
user369450

4
@JorgWMittag如果python中没有“类型”的概念,那么有人应该告诉谁写的文档:docs.python.org/3/library/stdtypes.html
Matt

@Matt是公平的,类型是在最近的3.5版本中加入的,特别是按允许使用的生产标准。
加里德·史密斯

Answers:


53

子类型是类型多态性的一种形式,其中一个子类型是与另一数据类型(超类型)通过可替代性的一些概念数据类型,这意味着程序元素,典型地子例程或函数,写入到父类型的元素进行操作也可以对子类型的元素进行操作。

如果S是的子类型T,则通常会写上子类型关系S <: T,以表示S可以在T期望使用类型项的上下文中安全地使用任何类型项。子类型化的精确语义至关重要地取决于给定编程语言中“在上下文中安全使用”的含义的特殊性。

子类化不应与子类型混淆。通常,子类型化建立is-a关系,而子类化仅重用实现并建立语法关系,而不一定是语义关系(继承不能确保行为子类型化)。

为了区分这些概念,子类型化也称为接口继承,而子类化则称为实现继承代码继承。

参考
子类型
继承


1
说得好。在这个问题的上下文中可能值得一提的是C ++程序员经常使用纯虚拟基类来将子类型关系传达给类型系统。当然,通常首选通用编程方法。
Aluan Haddad

6
“子类型化的精确语义关键取决于给定编程语言中“在上下文中安全使用”的含义的具体情况。” …并且LSP定义了“安全”的含义的某种合理概念,并告诉我们这些细节必须满足哪些约束才能实现这种特定形式的“安全”。
约尔格W¯¯米塔格

另一个例子:如果我正确理解,在C ++中,public继承会引入一个子类型,而private继承会引入一个子类。
昆汀

@Quentin公共继承既是子类型又是子类,但私有继承只是子类,而不是子类型。你可以有不与像Java接口结构继承子类型
eques

27

一个类型,在我们这里所说的背景下,实际上是一组行为的保证。一个合同,如果你愿意。或者,从协议 Small Small借用术语。

是方法束。这是一组行为实现

子类型化是完善协议的一种方法。子类化是差分代码重用的一种方法,即仅通过描述行为差异来重用代码。

如果您使用过Java或C♯,那么您可能会想到所有类型都应该是interface类型的建议。实际上,如果您阅读了William Cook的《理解数据抽象,复习,那么您可能知道,使用这些语言进行OO,必须仅使用interfaces作为类型。(还有一个有趣的事实:Java interface直接来自于Objective-C的协议,而后者又直接来自Smalltalk。)

现在,如果我们遵循编码建议其逻辑结论和想象的Java版本,其中 interface s为类型,和类和原语都没有,那么一个interface从另一个继承将创建一个子类型关系,而一个class来自另一个意志继承仅用于通过进行差分代码重用super

据我所知,没有主流的静态类型语言严格区分继承代码(实现继承/子类化)和继承合同(子类型化)。在Java和C♯中,接口继承是纯子类型化(或者至少是直到Java 8中以及在可能的C until 8中引入默认方法为止),但是类继承也是子类型化和实现继承。我记得读过一篇关于实验性的静态类型的面向对象的LISP方言的文章,该方言严格区分了mixin包含行为),structs(包含状态),interfaces描述行为)和(由一个或多个mixin组成零个或多个结构并符合一个或多个接口)。只能实例化类,并且只能将接口用作类型。

在动态类型的OO语言(例如Python,Ruby,ECMAScript或Smalltalk)中,我们通常将对象的类型视为对象所遵循的协议集。请注意复数形式:一个对象可以具有多种类型,而我不仅在谈论一个事实,即每个类型String的对象也是一个type的对象Object。(顺便说一句:请注意我是如何使用类名来谈论类型的?我有多么愚蠢!)一个对象可以实现多种协议。例如,在Ruby中,Arrays可以附加,可以建立索引,可以对其进行迭代以及可以进行比较。他们实现了四种不同的协议!

现在,Ruby没有类型。但是Ruby 社区有类型!但是,它们仅存在于程序员的头脑中。并在文档中。例如,任何each通过调用一个元素一个元素来响应方法的对象都被视为可枚举的对象。还有一个叫混入Enumerable取决于该协议。所以,如果你的对象具有正确的类型(只存在于程序员的头),则允许在(继承)的混合Enumerable混入,并顺利拿到各种很酷的方法免费的,比如mapreducefilter等上。

同样,如果一个物体响应<=>,那么它被认为是实现可比的协议,它可以在混合Comparable混入,并得到这样的东西<<=><===between?,和clamp是免费的。但是,它本身也可以实现所有这些方法,而不是完全继承Comparable,因此仍被认为是可比较的

一个很好的例子是StringIO库,该库实质上是使用字符串伪造 I / O流。它实现了与IO该类相同的所有方法,但是两者之间没有继承关系。但是,StringIO可以在可以使用的任何地方IO使用a。这在单元测试中非常有用,在单元测试中,您可以替换文件或stdin以替换文件,StringIO而无需对程序进行任何进一步的更改。因为它们StringIO遵循与相同的协议IO,所以即使它们是不同的类,它们也属于同一类型,并且不共享任何关系(除了它们Object在某个点上的琐碎扩展之外)。


如果语言允许程序同时声明一个类类型和一个该类为其实现的接口,并且还允许实现指定“构造函数”(它将链接到该接口指定的类的构造函数),则可能会有所帮助。对于将公开共享其引用的对象类型,首选模式是仅在创建派生类时使用类类型;大多数引用应该是接口类型。在以下情况下,能够指定接口构造函数将很有帮助……
supercat

...例如,代码需要一个集合,该集合将允许通过索引读取一组特定的值,但实际上并不关心它是什么类型。尽管有充分的理由将类和接口识别为不同类型的类型,但是在许多情况下,它们应该能够比目前的语言更紧密地协作。
超级猫

您是否有参考文献或一些关键字,我可以搜索有关您提到的实验LISP方言的更多信息,这些方言正式区分了mixin,结构,接口和类?
电话

@tel:不,对不起。大概是15到20年前,那时我的兴趣无处不在。当我偶然发现这一点时,我不可能开始告诉你我在寻找什么。
约尔格W¯¯米塔格

Awww。这是所有这些答案中最有趣的细节。这些概念的正式分离实际上可能在某种语言的实现中实现,这一事实确实帮助我明确了类/类型的区别。我想无论如何我都会自己寻找LISP。您是否还记得是否在期刊文章/书中读到过它,或者只是在对话中听说过它?
电话

2

首先区分类型和类,然后深入研究子类型化和子类化之间的区别也许是很有用的。

对于此答案的其余部分,我将假设讨论中的类型是静态类型(因为子类型通常在静态上下文中出现)。

我将开发一个玩具伪代码来帮助说明类型和类之间的区别,因为大多数语言至少将它们部分地融合在一起(出于充分的理由,我将简要介绍一下)。

让我们从一个类型开始。类型是代码中表达式的标签。可以通过外部程序(类型检查器)来确定此标签的值以及它与所有其他标签的值是否一致(对于某些类型的系统特定的一致定义),而无需运行您的程序。这就是使这些标签与众不同并应有自己名字的原因。

在我们的玩具语言中,我们可能允许像这样创建标签。

declare type Int
declare type String

然后,我们可以将各种值标记为这种类型。

0 is of type Int
1 is of type Int
-1 is of type Int
...

"" is of type String
"a" is of type String
"b" is of type String
...

有了这些语句,我们的类型检查器现在可以拒绝诸如

0 is of type String

如果我们的类型系统的要求之一是每个表达式都有一个唯一的类型。

现在让我们暂且不谈这是多么笨拙,以及如何分配无限数量的表达式类型会遇到问题。我们可以稍后再返回。

另一方面,类是组合在一起的方法和字段的集合(可能带有访问修饰符,例如private或public)。

class StringClass:
  defMethod concatenate(otherString): ...
  defField size: ...

此类的实例可以创建或使用这些方法和字段的预先定义。

我们可以选择将一个类与一个类型相关联,以使一个类的每个实例都自动用该类型标记。

associate StringClass with String

但是并不是每个类型都需要有一个关联的类。

# Hmm... Doesn't look like there's a class for Int

也可以想象,在我们的玩具语言中,并非每个类都有一个类型,特别是如果不是所有的表达式都具有类型。想象一下,如果某些表达式具有类型而有些则没有,那么类型系统一致性规则看起来会有些棘手(但并非不可能)。

而且,在我们的玩具语言中,这些关联不必是唯一的。我们可以将两个具有相同类型的类相关联。

associate MyCustomStringClass with String

现在请记住,我们的类型检查器不需要跟踪表达式的值(在大多数情况下,这样做不会或不可能这样做)。它所知道的就是您告诉过的标签。提醒一下,0 is of type String由于我们的人为创建的类型规则,表达式必须具有唯一的类型,而我们已经将表达式标记为0其他名称,因此类型检查器只能拒绝该语句。它对的价值没有任何特殊的了解0

那么子类型化呢?好的子类型是类型检查中一条通用规则的名称,它可以放宽您可能拥有的其他规则。也就是说,如果A is subtype of B您的类型检查器到处都需要的标签B,它也会接受A

例如,我们可能会对数字进行以下操作,而不是对以前的操作进行以下操作。

declare type NaturalNum
declare type Int
NaturalNum is subtype of Int

0 is of type NaturalNum
1 is of type NaturalNum
-1 is of type Int
...

子类化是声明一个新类的简写,该类使您可以重用以前声明的方法和字段。

class ExtendedStringClass is subclass of StringClass:
  # We get concatenate and size for free!
  def addQuestionMark: ...

我们不必副实例ExtendedStringClassString像我们那样用StringClass,因为毕竟这是一个全新的类,我们只是没有写的一样多。从ExtendedStringClass类型检查器String的角度来看,这将允许我们提供与类型不兼容的类型。

同样,我们本来可以决定上一堂全新的课NewClass并完成

associate NewClass with String

现在StringClassNewClass从类型检查器的角度来看,可以用的每个实例替换。

因此,从理论上讲,子类型化和子类化是完全不同的东西。但据我所知,没有语言具有类型和类实际上是通过这种方式执行操作的。让我们开始精简一下语言,并解释一些决策背后的理由。

首先,即使从理论上说,完全不同的类可以被赋予相同的类型,或者一个类可以被赋予与不是任何类的实例的值相同的类型,但这严重地妨碍了类型检查器的实用性。类型检查器实际上失去了检查您在表达式中调用的方法或字段是否确实存在于该值上的能力,这可能是您想要与a一起玩的麻烦时要进行的检查类型检查器。毕竟,谁知道该String标签下的实际值是多少?它可能是根本没有的东西,例如根本没有concatenate方法!

好的,让我们规定每个类自动生成一个与该类同名的新类型,并自动生成associate具有该类型的s实例。这让我们摆脱associate,以及之间的不同的名称StringClassString

出于相同的原因,我们可能希望自动在两个类的类型之间建立子类型关系,其中一个是另一个的子类。毕竟,子类被保证具有父类具有的所有方法和字段,但事实并非如此。因此,尽管子类可以在任何时候需要父类的类型时通过,但是如果需要子类的类型,则应拒绝父类的类型。

如果将此与所有用户定义的值都必须是类的实例的规定结合在一起,则可以具有is subclass of双重职责并摆脱is subtype of

这使我们了解到大多数流行的静态类型的OO语言所共有的特征。有一组“原始”类型(例如intfloat等),它们与任何类都不相关并且不是用户定义的。然后,您将拥有所有用户定义的类,这些类将自动具有相同名称的类型,并通过子类型识别子类。

我最后要说明的是围绕声明值和类型分开的笨拙之处。大多数语言将两者的创建混为一谈,因此类型声明也是一种用于生成全新值的声明,这些值会自动用该类型标记。例如,类声明通常同时创建类型和实例化该类型的值的方式。这消除了一些笨拙,并且在构造函数存在的情况下,还使您可以一次敲击一个类型来创建无限多个标签。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.