Golang: Pattern: Override Struct Method

 15th October 2021 at 10:52am

Golang 并没有类继承的机制,但是有类似的方法可以实现 struct 方法的覆盖。

比如下面这段 Sarama 库中的代码:

type Client interface {
	Broker(brokerID int32) (*Broker, error)
	Close() error
  // 省略掉大量无关的函数定义
}
// nopCloserClient embeds an existing Client, but disables
// the Close method (yet all other methods pass
// through unchanged). This is for use in larger structs
// where it is undesirable to close the client that was
// passed in by the caller.
type nopCloserClient struct {
	Client
}

// Close intercepts and purposely does not call the underlying
// client's Close() method.
func (ncc *nopCloserClient) Close() error {
	return nil
}

这段代码的核心在于把一个 Client 的 interface 变量作为 nopCloserClient 的对象。nopCloserClient 的对象仍然被认为是个 Client,是可以覆值给 Client 的 interface 的。

// NewConsumerFromClient creates a new consumer using the given client. It is still
// necessary to call Close() on the underlying client when shutting down this consumer.
func NewConsumerFromClient(client Client) (Consumer, error) {
	// For clients passed in by the client, ensure we don't
	// call Close() on it.
	cli := &nopCloserClient{client}
	return newConsumer(cli)
}

Client 是个带 Close 函数的 interface。nopCloserClient “继承” 了 Client 接口,但是通过编写额外的 Close 函数使得其没有效果。

再写一段简单的代码演示之:

package main

import (
	"fmt"
)

type Client interface {
	Close()
}

type NormalClient struct {
}

func (c NormalClient) Close() {
	fmt.Println("Closing")
}

type NopClient struct {
	Client
}

func (c NopClient) Close() {
	fmt.Println("Do nothing")
}

func main() {
	normal := NormalClient{}
	nop := NopClient{Client: normal}
	nop.Close()        // => "Do nothing"
	nop.Client.Close() // => "Closing"

	// 定义一个 client 的 interface 值,nop 可以被赋值给 client
	var client Client = nop
	client.Close() // => "Do nothing"
}

本质上,Golang 通过「一个 struct 可以有一或多个匿名 interface field」,来实现了多 组合继承 的效果、以及 可以覆盖“父类”的实现。所谓 组合继承 是我编的一个概念:

  • 对于一般编程语言,继承的是具体的类,比如 Triangle 类继承了 Shape
  • 对于 Go 而言,继承的实际上是接口而不是类。比如 FileReader 如果继承了 io.Closer 接口,那么视具体的 fileReader 对象中的 io.Closer interface 值底下的具体类型,可以是 ACloser 也可以是 BCloser;这时调用 fileReader.Close() 就会调用到具体的 ACloser.Close() 或是 BCloser.Close(),非常灵活