“鸭子类型"也叫"鸭式辨型"等, 英文叫"Duck Typing”.
下面这句话不知道是谁说的, 只记得之前在读《Javascript权威指南》
时遇到过
当看到一只动物走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只动物就可以被称为鸭子。
按我的话来说, 一个东西, 我不想管它是什么, 我只管它能做什么.
在程序语言中, 这种所谓的"鸭子类型"表现有两种形式: 动态和静态.
动态的例子:
def work(obj):
obj.work()
class Programmer:
def work(self):
print("coding")
class Student:
def work(self):
print("learning")
if __name__ == '__main__':
s = Student()
p = Programmer()
work(s)
work(p)
代码开始定义的work(obj)
函数是说: 你给我一个东西, 它只要能work()
就行, 至于它是什么, 无所谓.
十分灵活.
但是这有个问题, 如果你传入一个没有work()方法的obj, 运行期间会报错, 比如:
# AttributeError: 'dict' object has no attribute 'work'
work({})
那么很明显, 如果我写了这个work函数, 别人随便传的话, 很容易出错, 这也是动态类型的缺点, 很难有效的限制参数的类型, 运行期容易出现类型方面的错误.
正所谓
动态类型一时爽,代码重构火葬场.
那么静态的怎么样呢?
以传统的C++, Java来说, 你必须实现相应的接口才可以, 不然编译都过不了, 这也就避免了一些因类型不对导致的问题:
// Obj.java
package com.test.service;
// 定义接口
public interface Obj {
public void work();
}
// Student.java
package com.test.impl;
import com.test.service.Obj;
// Student实现了Obj接口, 所以可以work()
public class Student implements Obj {
public void work() {
System.out.println("learning");
}
}
// FuErDie.java
package com.test.impl;
// FuErDie没有实现Obj接口, 所以不能work
public class FuErDie {
public void play() {
System.out.println("play...");
}
}
// Application.java
package com.test.app;
import com.test.impl.FuErDie;
import com.test.impl.Student;
import com.test.service.Obj;
public class Application {
public static void work(Obj obj) {
obj.work();
}
public static void main(String[] args) {
Student s = new Student();
work(s); // 输出 learning
FuErDie f = new FuErDie();
// 报错: The method work(Obj) in the type Application is not applicable for the arguments (FuErDie)
work(f);
}
}
上面的例子很好地体现了静态语言的一些优势: 编译期间就可以检测到一些类型方面的错误.
而go语言的Duck Typing有点特别, 它结合了动态语言的灵活性,同时又会进行静态语言的类型检查,写起来是比较方便的。go 采用了折中的做法:不要求类型显示地声明实现了某个接口,只要实现了相关的方法即可,编译器就能检测到。
package main
import (
"fmt"
)
type Work interface {
work()
}
type Student struct {}
func (s *Student) work() {
fmt.Println("learning")
}
func (s Student) String() string {
return "不告诉你"
}
type FuErDie struct {}
func (f *FuErDie) play() {
fmt.Println("play...")
}
func work(obj Work) {
obj.work()
}
func main() {
s := &Student{}
work(s)
f := &FuErDie{}
// ./main.go:35:6: cannot use f (type *FuErDie) as type Work in argument to work:
// *FuErDie does not implement Work (missing work method)
work(f)
fmt.Println(s)
}
可以看到, 在go中实现一个接口, 不需要显示地写"implement xxx", 只要实现其中的方法就行.
而且接口与其具体的实现之间几乎没有耦合.
所以在go中, 你可能无意间实现了很多接口.
欢迎补充指正!
(完)