垂直组合与水平组合

垂直组合与水平组合

垂直组合 #

Go 语言为支撑组合的设计提供了类型嵌入(Type Embedding)。通过类型嵌入,我们可以将已经实现的功能嵌入到新类型中,以快速满足新类型的功能需求,这种方式有些类似经典面向对象语言中的“继承”机制,但在原理上却与面向对象中的继承完全不同,这是一种 Go 设计者们精心设计的“语法糖”。

被嵌入的类型和新类型两者之间没有任何关系,甚至相互完全不知道对方的存在,更没有经典面向对象语言中的那种父类、子类的关系,以及向上、向下转型(Type Casting)。通过新类型实例调用方法时,方法的匹配主要取决于方法名字,而不是类型。这种组合方式,我称之为垂直组合,即通过类型嵌入,快速让一个新类型“复用”其他类型已经实现的能力,实现功能的垂直扩展。

你可以看看下面这个 Go 标准库中的一段使用类型嵌入的组合方式的代码段:

// $GOROOT/src/sync/pool.go
type poolLocal struct {
    private interface{}
    shared  []interface{}
    Mutex
    pad     [128]byte
}

在代码段中,我们在 poolLocal 这个结构体类型中嵌入了类型 Mutex,这就使得 poolLocal 这个类型具有了互斥同步的能力,我们可以通过 poolLocal 类型的变量,直接调用 Mutex 类型的方法 Lock 或 Unlock。

另外,我们在标准库中还会经常看到类似如下定义接口类型的代码段:

// $GOROOT/src/io/io.go
type ReadWriter interface {
    Reader
    Writer
}

这里,标准库通过嵌入接口类型的方式来实现接口行为的聚合,组成大接口,这种方式在标准库中尤为常用,并且已经成为了 Go 语言的一种惯用法。

水平组合 #

垂直组合本质上是一种“能力继承”,采用嵌入方式定义的新类型继承了嵌入类型的能力。Go 还有一种常见的组合方式,叫水平组合。和垂直组合的能力继承不同,水平组合是一种能力委托(Delegate),我们通常使用接口类型来实现水平组合。

Go 语言中的接口是一个创新设计,它只是方法集合,并且它与实现者之间的关系无需通过显式关键字修饰,它让程序内部各部分之间的耦合降至最低,同时它也是连接程序各个部分之间“纽带”。

水平组合的模式有很多,比如一种常见方法就是,通过接受接口类型参数的普通函数进行组合,如以下代码段所示:

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

也就是说,函数 ReadAll 通过 io.Reader 这个接口,将 io.Reader 的实现与 ReadAll 所在的包低耦合地水平组合在一起了,从而达到从任意实现 io.Reader 的数据源读取所有数据的目的。类似的水平组合“模式”还有点缀器、中间件等,这里我就不展开了,在后面讲到接口类型时再详细叙述。

此外,我们还可以将 Go 语言内置的并发能力进行灵活组合以实现,比如,通过 goroutine+channel 的组合,可以实现类似 Unix Pipe 的能力。

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

Viewpoint #

From #