简介
不同于 C++、Java 那样侵入式的接口设计,Go 独特的非侵入式接口设计是 Go 语言的一个亮点,它使得 Go 这样一门静态编译型语言有了动态解释型语言的特性,提供了非常大的灵活性。可以说 Go 语言的成功,接口功不可没。
Go 接口和 C++ 接口的异同点
接口定义了一种规范,描述了类的行为和功能但不做具体实现。
C++的接口使用抽象类实现,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类,例如:
class Shape
{
public:
// 纯虚 func
virtual double getArea() = 0;
private:
string name;
};
其中的 getArea() 就是纯虚函数。
设计抽象类的目的是为了给其他类提供一个可以继承的基类。
抽象类不能被用于实例化对象,只能作为接口使用,派生类需要明确地声明它继承自基类,并且需要实现基类中声明的所有纯虚函数。
C++ 这种定义接口的方式称为"侵入式",而 Go 采用的是“非侵入式”,不需要显示声明,只需要实现接口定义的所有函数,编译器就会自动识别。
C++ 和 Go 在定义接口方式上的不同,也导致了在底层实现上的差异。
- C++ 通过虚函数表来实现基类调用派生类的函数,而 Go 是通过 itab 结构体中的 fun 字段来实现接口变量调用实体类型的函数。
- C++ 中的虚函数表是在编译期间生成的,而 Go 的 itab 中的 fun 字段是在运行期间动态生成的,由于非侵入式接口设计,Go 程序中实体类型可能会无意中实现了 N 个接口,而很多接口并不是开发人员需要的,所以不能为类型实现的所有接口都生成一个 itab。这种情况在 C++ 中就不会存在,因为派生类需要显示声明它继承自哪个基类。
Go 语言与“鸭子类型🦆”的关系
if it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
如果某个东西长的像鸭子,像鸭子一样会游泳、嘎嘎叫,那它就可以被看成是一只鸭子。
Duck Typing,鸭子类型是动态编程语言的一种对象推断策略,更关注对象能如何被使用,而不是关注对象的类型本身。Go 语言作为一门静态语言,它通过接口的方式完美的支持鸭子类型。
静态语言和动态语言
在动态语言如 python 中,定义了一个函数,那么调用此函数时,可以传入任意类型,只要它实现了这个函数即可,如果没有实现运行过程中就会报错;
在静态语言如果 Java、C++ 中,类型必须显示地声明实现了某个接口之后,才能够用在任何需要这个接口的地方,如果在程序调用以接口为参数的函数时,却传入了一个没有实现该接口方法的类型,那么在编译阶段就会报错,这也是静态语言比动态语言更加安全的原因。
- 静态语言在编译期间就能发现类型不匹配的错误,而动态语言必须运行到出错的那行代码才会报错。
- 静态语言要求程序员在编码阶段就要按照规定编写程序,为每个变量规定数据类型,增加了工作量和代码量;而动态语言类型则没有这些要求,比如 javascript 就是无类型语言,一个 let 关键字可以定义通用类型,因此可以让程序员更专注在业务上,代码也更短,写起来也更快。
Go 语言作为现代的静态语言,是有后发优势的,它引入了动态语言的便利(自动类型推导),同时又会进行静态语言的类型检查。
Go 实际上采用了折中的做法,不要求类型显式的声明实现了某个接口,只要实现了接口的所有方法即可,剩下的交由编译器去检测。
总结
鸭子类型是一种动态语言的风格,在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口决定,而是由它"当前方法和属性的集合"决定。
Go 作为一门静态语言,通过接口实现了鸭子类型,引入了动态语言的特性,实际上是因为 Go 的编译器在其中作了隐匿的转换工作。
本篇是接口部分的第一篇基础介绍,后面会从源码、示例方面进一步分析。