5.12. 内置

Go 不提供通行的、类型驱动的子类划分的概念,但它通过在结构或界面内置,确实有能力从某实现“借”些片段。

界面内置非常简单。我们提到过 io.Reader 和 io.Writer 的界面;这里是其定义:

  type Reader interface {
      Read(p []byte) (n int, err os.Error)
  }

  type Writer interface {
      Write(p []byte) (n int, err os.Error)
  }

io 包也导出一些其它的界面来规范实现其多个方法的物件。例如,io.ReadWriter 界面同时包括 Read 和 Write 。我们可以明确的列出这两个方法来规定 io.ReadWriter ,但更简单更有启发的是内置这两个界面形成新界面,如下:

  // ReadWrite is the interface that groups the basic Read and Write methods.
  type ReadWriter interface {
      Reader
      Writer
  }

顾名思意,ReadWriter 可做 Reader 和 Writer 两个的事;它是内置界面的集合(必须是没有交集的方法)。只有界面可以内置在界面里。

同样的基本概念适用于结构,但牵连更广。bu?o 包有两个结构类型,bu?o.Reader 和 bu?o.Writer ,每个都当然对应实现着 io 包的界面。并且,bu?o 也实现了缓冲的读写,这是通过把 reader 和 writer 内置到一个结构做到的;它列出类型但不给名称:

  // ReadWriter stores pointers to a Reader and a Writer.
  // It implements io.ReadWriter.
  type ReadWriter struct {
      *Reader  // *bufio.Reader
      *Writer  // *bufio.Writer
  }

内置元素是指针所以使用前必须初始化指向有效的结构。 ReadWriter 结构可以写为:

  type ReadWriter struct {
      reader *Reader
      writer *Writer
  }

但要提升域的方法又要满足 io 界面,我们还需提供转发方法,如:

  func (rw *ReadWriter) Read(p []byte) (n int, err os.Error) {
      return rw.reader.Read(p)
  }

通过直接内置结构,我们避免了这些账目管理。内置类型的方法是附送的,亦即 bu?o.ReadWriter 除了有 bu?o.Reader 和bu?o.Writer 的方法,还同时满足三个界面:io.Reader,io.Writer 和 io.ReadWriter 。

内置和子类划分有着重要不同。我们内置类型时,此类的方法成为外层类型的方法,但调用时其接受者是内层类型,而不是外层。我们的例子里,当 bu?o.ReadWriter 的 Read 方法被调用时,它的效果和上面所写的转发方法完全一样;接受者是 ReadWriter 的 reader,而不是 ReadWriter 自身。

内置也用来提供某种便利。下例是一个内置域和普通的,带名的域在一起:

  type Job struct {
      Command string
      *log.Logger
  }

此时 Job 类型有 Log,Logf 和其它 *log.Logger 的方法。 我们当然可以给 Logger 个名字,但无此必要。这里,初始化后,我们可以 log Job:

  job.Log("starting now...")

Logger 是结构的普通域所以我们能用通常的架构函数初始化它:

  func NewJob(command string, logger *log.Logger) *Job {
      return 
  &Job{command, logger}
  }

或使用组合字面:

  job := &Job{command, log.New(os.Stderr, nil, "Job: ", log.Ldate)}

如果我们需要直接引用内置域,域的类型名,忽略包标识,可作为域名。如果我们要得到 Job 变量 job 的 *log.Logger,我们用job.Logger 。这可用在细化 Logger 的方法上:

  func (job *Job) Logf(format string, args ...) {
      job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args))
  }

内置类型导致撞名的问题,但解决方案很简单。首先,域或方法 X 隐藏此类型更深层部分的 X 项。如果 log.Logger 包括叫 Command 的域或方法,则 Job 的 Command 域占主导权。

其次,如果同层出现同名,通常是个错误;如果 Job 结构有另一域或方法叫 Logger,要内置 log.Logger 会出错。但只要重名在类型定义外的程序中不被提及,就不成问题。此条件提供了改动外部的内置类型的某种保护;只要两个域都没被用到,新增的域名和另一子类的域重名也没有问题。