Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

11 Jan 2015

C++的面向对象和Go语言接口方式的比较

C语言中的struct只能表示数据的集合,对数据集合的操作要在单独的函数中实现。比如我们在C语言中定义一个Cat的结构体,Cat有meow()以及sleep()两种行为:

我们把color和breed看作Cat的属性;meow()和sleep()看作是Cat的两个方法。但是在C语言中,属性color和breed是属于Cat结构体的,而方法meow()和sleep()却并不属于Cat结构体,并且没有一个概念能把这两个方法集合到一起,也就是说无法像C++那样创建一个结构体CatBehavior,把这两个方法集合在一起:

为此,C++引入了封装的概念,让C的struct支持方法。在C++中,你可以把属性和方法放一起了:

但是这样还不够,面向对象的准则是只暴露接口而不暴露内部的方法属性,这样类与类使用者之间的耦合度才能降低,修改类的内部实现不至于对类的既有使用者造成太大影响。

所以下一步,我们要创建一个接口类CatBehavior来定义Cat的两种行为meow和sleep:

Cat继承并实现CatBehavior:

上述代码翻译成Go语言是这样子的:

Go和C++最大的区别是Go中的接口并不是通过继承来实现的,而是一种更松散的关系,只要Cat的方法中具备了CatBehavior接口中列举的所有方法,就可以说Cat实现了CatBehavior。

大部分时候,Go语言这种松散的接口就足够了,使用C++这种继承体制反而会营造出很多不必要的的类继承关系。举个例子,假设你家里既养了猫又养了狗,每天晚上你要让它们上床睡觉,需要goToBed函数:

那么,在C++中类关系可能会这样:

上面代码的继承结构有三级,第一级Sleepable,第二级CatBehavior和DogBehavior,第三级才是Cat和Dog。

在Go语言中就不需要这么层级了,接口Sleepable和CatBehavior和DogBehavior可以是同一级:

那么在Go语言中能不能模拟出像C++接口继承这种父子关系呢,或许可以这样做:

一些总结

  • 接口封装是必须的,但是继承和多态并不是必须的。而且继承和多态容易被误用而过度设计,产生庞大的类型继承体系,导致复杂的依赖关系以及难以理解的代码。
  • Go语言的接口方式是对继承和多态的一种简化,在Go中类型与类型的关系是平的而不是树状的。
  • Go语言的接口方式有点泛型的味道,泛型的实用性其实比继承和多态要高。虽然C++是面向对象语言,但是它的标准库STL却是一个泛型库。
  • C++中的多态需要运行时才能确定被调用的函数,而Go中的接口是在编译的时候定下来的,有一定性能上的优势。