子类型

子类型一般是与超类型有关系的数据类型。通常使用里式替换原则(Liskov Substitution principle)定义,虽然很相似,但却与面向对象的继承是不同的概念,子类型仅仅是描述两种类型之间的关系,而这两种类型间可以没有语义上的联系;继承却是编程语言的特性,要求一种类型可以由另一种创造出来。

子类型关系是自反的和传递的,是类型上的预序关系。

子类型分为两种:

(1) 名义子类型,在其中只有类型名相同的才是相同类型,子类型关系必须被显式声明。比如,C 的 struct,除非使用 typedef,否则两个不同名字的 struct 必然是不同类型。C++ 中 class 的继承关系,如果加上“一个类是自己的子类”的条件,那就是一个子类型关系了,因为传递性是容易证明的:

#include <iostream>
using namespace std;
class A {
};

class B : public A {
};

class C : public B {
};

void func(A a) {
}

int main() {
    C c;
    func(c);
}

(2) 结构子类型,两种类型的结构决定了其间的子类型关系。这个在 Go 语言中再明显不过了:

package main

import "fmt"

func main() {
    a := A{100}
    foo(B(a))
}

func foo(b B) {
    b.call()
}

type A struct {
    val int
}

func (a *A) call() {
    fmt.Println("val:", a.val)
}

type B struct {
    val int
}

func (b *B) call() {
    fmt.Println("val:", b.val)
}

输出:

100

结构体 A 类型的变量 a 被强制转换为结构体 B 类型,这要是在 C 语言中,一定会出现这样的错误:

error: conversion to non-scalar type requested

因为 C 语言中规定只能向标量类型转换,而 struct 是聚合类型。Go 语言却运行并且给出了正确的结果,但是,如果我们给 B 加上一个字段:

type B struct {
    val  int
    val2 int
}

果然出现了错误:

: cannot convert a (type A) to type B

这里就是结构子类型的应用,只要 A 和 B 类型的结构是相同的,那 A 和 B 就是可以相互转换,即等价。

Go 语言的非侵入式接口

这是 Go 语言很突出的一个特性。Ruby 之父也在《代码的未来》中对这个特性大加称赞(至少中文版里是这样的,因为日文版的 PDF 我实在找不到)。

因为讲到这个特性的文章实在太多,所以我就不再抄一遍过来了。贴一篇我觉得还可以的:Go 对 OO 的选择

鸭子类型

因为非侵入的 interface,Go 语言实现了动态语言所推崇的鸭子类型,即:某个类型是否满足某个接口,不需要事先进行声明,只要实现了接口的所有方法,就满足该接口。

虽然很相似,但鸭子类型与结构子类型也是不同的。结构子类型由类型的结构决定类型的兼容性和等价性,而鸭子类型只由结构中在运行时所访问的部分决定类型的兼容性。也就是说,前面的 A 与 B 类型的相互转换与鸭子类型无关。而通过 interface 来调用方法时,Go 会根据对象来选择合适的方法进行调用,这个才是鸭子类型。