如何向 Go 中的现有类型添加新方法?

How to add new methods to an existing type in Go?

我想在 gorilla/mux 路由和路由器类型上添加一个方便的 util 方法:

package util

import(
    "net/http"
    "github.com/0xor1/gorillaseed/src/server/lib/mux"
)

func (r *mux.Route) Subroute(tpl string, h http.Handler) *mux.Route{
    return r.PathPrefix("/" + tpl).Subrouter().PathPrefix("/").Handler(h)
}

func (r *mux.Router) Subroute(tpl string, h http.Handler) *mux.Route{
    return r.PathPrefix("/" + tpl).Subrouter().PathPrefix("/").Handler(h)
}

但是编译器告诉我

Cannot define new methods on non-local type mux.Router

那么我该如何实现呢?我是否创建一个具有匿名 mux.Route 和 mux.Router 字段的新结构类型?还是别的?

正如编译器所提到的,您不能扩展另一个包中的现有类型。您可以定义自己的别名或子包如下:

type MyRouter mux.Router

func (m *MyRouter) F() { ... }

或通过嵌入原始路由器:

type MyRouter struct {
    *mux.Router
}

func (m *MyRouter) F() { ... }

...
r := &MyRouter{router}
r.F()

我想扩展 @jimt 给出的答案。这个答案是正确的,并且极大地帮助我解决了这个问题。但是,我遇到麻烦的两种方法(别名,嵌入)都有一些注意事项。

注意:我使用术语 parent 和 child,但我不确定那是最好的组合。基本上,parent 是您要在本地修改的类型。 Child 是尝试实现该修改的新类型。

方法 1 - 类型定义

type child parent
// or
type MyThing imported.Thing
  • 提供对字段的访问。
  • 不提供对方法的访问。

方法 2 - 嵌入 (official documentation)

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • 提供对字段的访问。
  • 提供对方法的访问。
  • 需要考虑初始化。

总结

  • 使用组合方法嵌入的parent如果是指针则不会初始化。 parent 必须单独初始化。
  • 如果嵌入的parent是一个指针,在child初始化的时候没有初始化,会出现nil指针解引用错误。
  • 类型定义和嵌入案例都提供对 parent.
  • 字段的访问
  • 类型定义不允许访问 parent 的方法,但嵌入 parent 可以。

你可以在下面的代码中看到这一点。

working example on the playground

package main

import (
    "fmt"
)

type parent struct {
    attr string
}

type childAlias parent

type childObjParent struct {
    parent
}

type childPointerParent struct {
    *parent
}

func (p *parent) parentDo(s string) { fmt.Println(s) }
func (c *childAlias) childAliasDo(s string) { fmt.Println(s) }
func (c *childObjParent) childObjParentDo(s string) { fmt.Println(s) }
func (c *childPointerParent) childPointerParentDo(s string) { fmt.Println(s) }

func main() {
    p := &parent{"pAttr"}
    c1 := &childAlias{"cAliasAttr"}
    c2 := &childObjParent{}
    // When the parent is a pointer it must be initialized.
    // Otherwise, we get a nil pointer error when trying to set the attr.
    c3 := &childPointerParent{}
    c4 := &childPointerParent{&parent{}}

    c2.attr = "cObjParentAttr"
    // c3.attr = "cPointerParentAttr" // NOGO nil pointer dereference
    c4.attr = "cPointerParentAttr"

    // CAN do because we inherit parent's fields
    fmt.Println(p.attr)
    fmt.Println(c1.attr)
    fmt.Println(c2.attr)
    fmt.Println(c4.attr)

    p.parentDo("called parentDo on parent")
    c1.childAliasDo("called childAliasDo on ChildAlias")
    c2.childObjParentDo("called childObjParentDo on ChildObjParent")
    c3.childPointerParentDo("called childPointerParentDo on ChildPointerParent")
    c4.childPointerParentDo("called childPointerParentDo on ChildPointerParent")

    // CANNOT do because we don't inherit parent's methods
    // c1.parentDo("called parentDo on childAlias") // NOGO c1.parentDo undefined

    // CAN do because we inherit the parent's methods
    c2.parentDo("called parentDo on childObjParent")
    c3.parentDo("called parentDo on childPointerParent")
    c4.parentDo("called parentDo on childPointerParent")
}

扩展其他答案之一,在我的例子中,父级是一个数组。如果你 想添加方法,又能访问到父方法,必须wrap 定义类型时,声明变量时换行:

package main

type parent []int

func (p parent) first() int {
   return p[0]
}

type child struct {
   parent
}

func (c child) second() int {
   return c.parent[1]
}

func main() {
   a := child{
      parent{1, 2},
   }
   first := a.first()
   second := a.second()
   println(first == 1, second == 2)
}