747 字
4 分钟
鸭子类型

我们可以通过一个小故事来更深刻地了解鸭子类型。

从前在 JavaScript王国里,有一个国王,他觉得世界上最美妙的声音就是鸭子的叫 声,于是国王召集大臣,要组建一个 1000 只鸭子组成的合唱团。大臣们找遍了全国, 终于找到 999只鸭子,但是始终还差一只,最后大臣发现有一只非常特别的鸡,它的叫 声跟鸭子一模一样,于是这只鸡就成为了合唱团的最后一员。

这个故事告诉我们,国王要听的只是鸭子的叫声,这个声音的主人到底是鸡还是鸭并不重要。 鸭子类型指导我们只关注对象的行为,而不关注对象本身,也就是关注 HAS-A, 而不是 IS-A。

var duck = {
type: 'duck',
duckSinging: function () {
console.log('gagagag');
}
}
var chicken = {
type: 'chicken',
duckSinging: function () {
console.log('gagagag');
}
}
var choir = [];
var joinChoir = function (animal) {
if (animal && typeof animal.duckSinging === 'function') {
choir.push(animal);
console.log(`恭喜${animal.type}加入合唱团`);
console.log(`合唱团已有人数为${choir.length}`);
}
}
joinChoir(duck);
joinChoir(chicken);

我们看到,对于加入合唱团的动物,大臣们根本无需检查它们的类型,而是只需要保证它们 拥有 duckSinging 方法。如果下次期望加入合唱团的是一只小狗,而这只小狗刚好也会鸭子叫, 我相信这只小狗也能顺利加入。

在动态类型语言的面向对象设计中,鸭子类型的概念至关重要。利用鸭子类型的思想,我们 不必借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:“面向接口编程,而不是 面向实现编程*”。例如,一个对象若有 push 和 pop 方法,并且这些方法提供了正确的实现,它就 可以被当作栈来使用。一个对象如果有 length 属性,也可以依照下标来存取属性(最好还要拥 有 slice 和 splice 等方法),这个对象就可以被当作数组来使用。

静态类型语言中,要实现“面向接口编程”并不是一件容易的事情,往往要通过抽象类或 者接口等将对象进行向上转型。当对象的真正类型被隐藏在它的超类型身后,这些对象才能在类 型检查系统的“监视”之下互相被替换使用。只有当对象能够被互相替换使用,才能体现出对象 多态性的价值。

“面向接口编程”是设计模式中最重要的思想,但在 JavaScript语言中,“面向接口编程”的 过程跟主流的静态类型语言不一样,因此,在 JavaScript中实现设计模式的过程与在一些我们熟 悉的语言中实现的过程会大相径庭。

鸭子类型
https://nollieleo.github.io/posts/鸭子类型/
作者
翁先森
发布于
2020-03-16
许可协议
CC BY-NC-SA 4.0