头图

本文视频地址

Java 、C++等主流面向对象(以下简称 OO)语言通过复杂和庞大的、自上而下的类型体系、继承、显式接口实现等机制将程序的各个部分耦合起来,但在 Go 语言中我们找不到经典 OO 的语法元素、类型体系和继承机制,或者说 Go 语言本质上就不属于经典 OO 语言范畴。针对这种情况,很多人会问:那 Go 语言是如何将程序的各个部分有机地耦合在一起的呢?Go 语言遵从的设计哲学也是组合。

在介绍组合之前,我们可以先来了解一下 Go 在语法元素设计时是如何为组合哲学的应用打下坚实基础的。

在 Go 语言设计层面,Go 设计者为 gopher 们提供了语法元素供后续组合使用,包括:

  • Go 语言无类型体系(type hierarchy),类型之间是独立的,没有子类型的概念;
  • 每个类型都可以有自己的方法集合,类型定义与方法实现是正交独立的;
  • 接口(interface)与其实现之间"隐式关联";
  • 包(package)之间是相对独立的,没有子包的概念。

我们看到无论是包、接口还是一个个具体的类型定义(包括类型的方法集合),Go 语言为我们呈现了这样的一幅图景:一个个没有关联的积木,但每个积木又都很精彩。现在摆在面前的工作就是在这些积木之间以最适当的方式建立关联(耦合),形成一个"整体"。Go 采用了组合的方式,也是唯一的方式。
Go 语言提供了的最为直观的组合的语法元素就是type embedding,即类型嵌入。通过类型嵌入,我们可以将已经实现的功能嵌入到新类型中,以快速满足新类型的功能需求,这种方式有些类似经典 OO 的“继承”,但在原理上与经典 OO 的继承完全不同。这是一种 Go 精心设计的“语法糖”,被嵌入的类型和新类型两者之间没有任何关系,甚至相互完全不知道对方的存在,更没有经典 OO 那种父类、子类的关系以及向上、向下转型(type casting)。通过新类型实例调用方法时,方法的匹配取决于方法名字,而不是类型。这种组合方式,我称之为“垂直组合”,即通过类型嵌入,快速让一个新类型“复用”其他类型已经实现的能力,实现功能的垂直扩展。
下面是一个类型嵌入的例子:

Go标准库:sync/pool.go

  type poolLocal struct {
      ...
      Mutex   // Protects shared.
      ...
  }

我们在 poolLocal 这个 struct 中嵌入类型 Mutex,被嵌入的 Mutex 类型的方法集合会被提升到外面的类型中。比如,这里的 poolLocal 将拥有 Mutex 类型的 Lock 和 Unlock 方法。实际调用时,方法调用实际会被传给 poolLocal 中的 Mutex 实例。
我们在标准库中还经常看到类似如下的 interface 类型嵌入的代码:

  type ReadWriter interface {
      Reader
      Writer
  }

通过在 interface 中嵌入 interface type,实现接口行为的聚合,组成大接口,这种方式在标准库中很常见,已经成为了 Go 语言的一种常见的用法。

interface 是 Go 语言中真正的魔法,是 Go 语言的一个创新设计,它只是方法集合,并且它与实现者之间的关系是隐式的,它让程序内部各部分之间的耦合降至最低,同时它也是连接程序各个部分之间“纽带”。隐式的 interface 实现会不经意间满足:依赖抽象、里氏替换、接口隔离等原则,这在其他语言中是需要很"刻意"的设计谋划才能实现的,但在 Go interface 来看,一切却是自然而然的。

通过 interface 将程序内部各个部分组合在一起的方法,我这里称之为水平组合。水平组合的“模式”很多,比如:一种常见方法就是:通过接受 interface 类型参数的普通函数进行组合,例如下面代码。

  func ReadAll(r io.Reader) ([]byte, error)
  func Copy(dst Writer, src Reader) (written int64, err error)

ReadAll 通过 io.Reader 这个接口将 io.Reader 的实现与 ReadAll 所在的包低耦合的水平组合在一起了。类似的水平组合“模式”还有 wrapper、middleware 等。

此外,Go 语言内置的并发能力也可以通过组合的方式实现“对计算能力的串联”,比如:通过 goroutine+channel 的组合实现类似 Unix Pipe 的能力。

至此,组合原则的应用展示了 Go 程序的骨架结构。类型嵌入为类型提供的垂直扩展能力,interface 是水平组合的关键,它好比程序肌体上的“关节”,给予连接“关节”的两个部分各自“自由活动”的能力,而整体上又实现了某种功能。组合也让遵循“简单”原则的 Go 语言的表现力丝毫不逊色于其他复杂的主流编程语言。

image


面向加薪学习
18 声望1k 粉丝