接口组合


有时,可能会想要基于某个已定义的接口,并新增自己的行为,在 Go 中,这类似于结构中方法的查找,只要在定义接口时,内嵌想要的接口名称就可以了。例如:

package main

import "fmt"

type ParentTester interface {
    ptest()
}

type ChildTester interface {
    ParentTester
    ctest()
}

type Subject struct {
    name string
}

func (s *Subject) ptest() {
    fmt.Printf("ptest %s\n", s)
}

func (s *Subject) ctest() {
    fmt.Printf("ctest %s\n", s)
}

func main() {
    var tester ChildTester = &Subject{"Test"}
    tester.ptest()
    tester.ctest()
}

在上面,Subject必须实现ParentTesterChildTest中定义的全部行为,其实例才可以被指定ChildTest。你也可以接口中包含多个接口:

package main

import "fmt"

type SuperTester interface {
    stest()
}

type ParentTester interface {
    ptest()
}

type ChildTester interface {
    SuperTester
    ParentTester
    ctest()
}

type Subject struct {
    name string
}

func (s *Subject) stest() {
    fmt.Printf("stest %s\n", s)
}

func (s *Subject) ptest() {
    fmt.Printf("ptest %s\n", s)
}

func (s *Subject) ctest() {
    fmt.Printf("ctest %s\n", s)
}

func main() {
    var tester ChildTester = &Subject{"Test"}
    tester.stest()
    tester.ptest()
    tester.ctest()
}

如果多个接口间的行为重复定义了,就会出现 duplicate method 的错误。(这是个有争议性的特性,因为许多人认为,实际上虽然在接口语法上确实重复定义了行为,然而就 Duck typing 的精神来看,结构上只要有实现行为就可以了,事实上在其他语言中,像是 Java 中,类似的情况并不会发生编译错误,有关此议题,可参考golang/go 的 此 issue)。

虽然说这像是接口有了继承方面的语法,然而更精确地说,应该是行为的内嵌,因此,只要是有实现相关行为,就算没有被包含在某个接口中,也可以做接口转换:

package main

import "fmt"

type SuperTester interface {
    stest()
}

type ParentTester interface {
    ptest()
}

type ChildTester interface {
    SuperTester
    ParentTester
    ctest()
}

type Tester interface {
    stest()
    ptest()
    ctest()
}

type Subject struct {
    name string
}

func (s *Subject) stest() {
    fmt.Printf("stest %s\n", s)
}

func (s *Subject) ptest() {
    fmt.Printf("ptest %s\n", s)
}

func (s *Subject) ctest() {
    fmt.Printf("ctest %s\n", s)
}

func main() {
    var ctester ChildTester = &Subject{"Test"}
    var tester Tester = ctester
    tester.stest()
    tester.ptest()
    tester.ctest()
}

有些文件会说,在接口有组合关系时,子接口的实例可以指定给父接口,反之就不行,这种说法不能说是错,毕竟就上例来说,ChildTester接口的实例,被指定给ParentTester接口时,从编译器的角度来看,ChildTester接口确实是有ParentTester接口的行为;反过来的话,ParentTester接口被指定给ChildTester接口时,编译器是看不到ParentTester接口上,会有ChildTester接口行为的,当然会发生错误。

更精确来说,Go 本身并非基于类,没有提供继承语法,也就没有父接口、子接口的概念,以上仅仅只是以行为的内嵌实现了继承的概念,因而是就看不看得到相关的行为,来判断是否可通过编译。


展开阅读全文