当前位置:   article > 正文

go语言方法之通过嵌入结构体来扩展类型

go语言方法之通过嵌入结构体来扩展类型

        我们先来看看这个类型:

  1. import "image/color"
  2. type Point struct{ X, Y float64 }
  3. type ColoredPoint struct {
  4. Point
  5. Color color.RGBA
  6. }

        我们完全可以将ColoredPoint定义为一个有三个字段的struct,但是我们却将Point这个类型嵌 入到ColoredPoint来提供X和Y这两个字段。像我们之前中看到的那样,内嵌可以使我们在 定义ColoredPoint时得到一种句法上的简写形式,并使其包含Point类型所具有的一切字段, 然后再定义一些自己的。如果我们想要的话,我们可以直接认为通过嵌入的字段就是 ColoredPoint自身的字段,而完全不需要在调用时指出Point,比如下面这样。

  1. var cp ColoredPoint
  2. cp.X = 1
  3. fmt.Println(cp.Point.X) // "1"
  4. cp.Point.Y = 2
  5. fmt.Println(cp.Y) // "2"

        对于Point中的方法我们也有类似的用法,我们可以把ColoredPoint类型当作接收器来调用 Point里的方法,即使ColoredPoint里没有声明这些方法:

  1. red := color.RGBA{255, 0, 0, 255}
  2. blue := color.RGBA{0, 0, 255, 255}
  3. var p = ColoredPoint{Point{1, 1}, red}
  4. var q = ColoredPoint{Point{5, 4}, blue}
  5. fmt.Println(p.Distance(q.Point)) // "5"
  6. p.ScaleBy(2)
  7. q.ScaleBy(2)
  8. fmt.Println(p.Distance(q.Point)) // "10"

        Point类的方法也被引入了ColoredPoint。用这种方式,内嵌可以使我们定义字段特别多的复 杂类型,我们可以将字段先按小类型分组,然后定义小类型的方法,之后再把它们组合起 来。

        如果对基于类来实现面向对象的语言比较熟悉的话,可能会倾向于将Point看作一个基 类,而ColoredPoint看作其子类或者继承类,或者将ColoredPoint看作"is a" Point类型。但这 是错误的理解。请注意上面例子中对Distance方法的调用。Distance有一个参数是Point类 型,但q并不是一个Point类,所以尽管q有着Point这个内嵌类型,我们也必须要显式地选择 它。尝试直接传q的话你会看到下面这样的错误:

p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point

        一个ColoredPoint并不是一个Point,但他"has a"Point,并且它有从Point类里引入的Distance 和ScaleBy方法。如果你喜欢从实现的角度来考虑问题,内嵌字段会指导编译器去生成额外的 包装方法来委托已经声明好的方法,和下面的形式是等价的:

  1. func (p ColoredPoint) Distance(q Point) float64 {
  2. return p.Point.Distance(q)
  3. }
  4. func (p *ColoredPoint) ScaleBy(factor float64) {
  5. p.Point.ScaleBy(factor)
  6. }

        当Point.Distance被第一个包装方法调用时,它的接收器值是p.Point,而不是p,当然了,在 Point类的方法里,你是访问不到ColoredPoint的任何字段的。

         在类型中内嵌的匿名字段也可能是一个命名类型的指针,这种情况下字段和方法会被间接地 引入到当前的类型中(译注:访问需要通过该指针指向的对象去取)。添加这一层间接关系让我 们可以共享通用的结构并动态地改变对象之间的关系。下面这个ColoredPoint的声明内嵌了一 个*Point的指针。

  1. type ColoredPoint struct {
  2. *Point
  3. Color color.RGBA
  4. }
  5. p := ColoredPoint{&Point{1, 1}, red}
  6. q := ColoredPoint{&Point{5, 4}, blue}
  7. fmt.Println(p.Distance(*q.Point)) // "5"
  8. q.Point = p.Point // p and q now share the same Point
  9. p.ScaleBy(2)
  10. fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"

        一个struct类型也可能会有多个匿名字段。我们将ColoredPoint定义为下面这样:

  1. type ColoredPoint struct {
  2. Point
  3. color.RGBA
  4. }

        然后这种类型的值便会拥有Point和RGBA类型的所有方法,以及直接定义在ColoredPoint中的 方法。当编译器解析一个选择器到方法时,比如p.ScaleBy,它会首先去找直接定义在这个类 型里的ScaleBy方法,然后找被ColoredPoint的内嵌字段们引入的方法,然后去找Point和 RGBA的内嵌字段引入的方法,然后一直递归向下找。如果选择器有二义性的话编译器会报错,比如你在同一级里有两个同名的方法。

        方法只能在命名类型(像Point)或者指向类型的指针上定义,但是多亏了内嵌,有些时候我们 给匿名struct类型来定义方法也有了手段。

        下面是一个小trick。这个例子展示了简单的cache,其使用两个包级别的变量来实现,一个 mutex互斥量和它所操作的cache:

  1. var (
  2. mu sync.Mutex // guards mapping
  3. mapping = make(map[string]string)
  4. )
  5. func Lookup(key string) string {
  6. mu.Lock()
  7. v := mapping[key]
  8. mu.Unlock()
  9. return v
  10. }

        下面这个版本在功能上是一致的,但将两个包级吧的变量放在了cache这个struct一组内:

  1. var cache = struct {
  2. sync.Mutex
  3. mapping map[string]string
  4. }{
  5. mapping: make(map[string]string),
  6. }
  7. func Lookup(key string) string {
  8. cache.Lock()
  9. v := cache.mapping[key]
  10. cache.Unlock()
  11. return v
  12. }

        我们给新的变量起了一个更具表达性的名字:cache。因为sync.Mutex字段也被嵌入到了这个 struct里,其Lock和Unlock方法也就都被引入到了这个匿名结构中了,这让我们能够以一个简 单明了的语法来对其进行加锁解锁操作。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/675933
推荐阅读
相关标签
  

闽ICP备14008679号