什么是鸭子类型(duck typing)

884 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的的第10天

定义

鸭子类型来源于著名的“鸭子测试”:

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

同理,如果某个对象O具备类型T的属性和方法,那么就可以认为对象O就是类型T,这就是鸭子类型。

鸭子类型一般是在弱类型语言(如JavaScript、Python等)或者一些非侵入式接口的语言(如Go)才存在。

image.png

如果一个东西有两个孔,而且能插插头,那它就是插座(逃

示例

下面以JavaScript为例示范一下鸭子类型:

var socket = {  
    hasTwoHoles: true,  
    pluggable: true,  
    
    color: "black"  
};

var someAnimal = {  
    appearance: "feathers",  
    quack: function animal_quack(what) {  
        print(what + " whoof-whoof!");  
    },  
    eyes: "yellow"  
};  

function check(who) {  
    if ((who.appearance == "feathers") && (typeof who.quack == "function")) {  
        who.quack("I look like a duck!\n");  
        return true;  
    }  
    return false;  
}  

check(duck);  // true
check(someAnimal);  // true

其中,我们定义了两个对象,一个是socket,另一个是pigNose。从代码中可以看到,它们虽然是截然不同的两个对象,但是它们都有两个孔,都可以插插头,插了之后都有反应,因此可以认为它们都是一种“插座”。

运行结果如下:

image.png

与其他类型系统的比较

结构类型系统

鸭子类型和结构类型相似但与之不同。结构类型由类型的结构决定类型的兼容性和等价性,而鸭子类型只由结构中在运行时所访问的部分决定类型的兼容性

接口

接口可以提供鸭子类型的一些益处,但鸭子类型与之不同的是没有显式定义任何接口。例如,如果一个第三方Java库实现了一个用户不允许修改的类,用户就无法把这个类的实例用作一个自己定义的接口的实现,而鸭子类型允许这样做。

但是如果如果某种语言支持非侵入式的接口实现,那么该语言也支持鸭子类型(如Go)

模板或泛型

模板函数或方法在一个静态类型上下文中应用鸭子测试;这同时带来了静态和动态类型检查的一般优点和缺点。同时,由于在鸭子类型中,只有“在运行时被实际调用的”方法需要被实现,而模板要求实现“在编译时不能证明不可到达的”所有方法,因此鸭子类型更具有可伸缩性。

实例包括带有模板的C++语言和Java语言的泛型。