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

huangapple go评论99阅读模式
英文:

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

问题

我想在gorilla/muxRouteRouter类型上添加一个方便的工具方法:

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)
}

但是编译器告诉我:

> 无法在非本地类型mux.Router上定义新方法

那么我该如何实现这个功能呢?我需要创建一个新的结构体类型,其中包含一个匿名的mux.Routemux.Router字段吗?还是其他方法?

英文:

I want to add a convenience util method on to gorilla/mux Route and Router types:

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)
}

but the compiler informs me

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

So how would I achieve this? Do I create a new struct type that has an anonymous mux.Route and mux.Router fields? Or something else?

答案1

得分: 258

根据编译器的提示,你不能在另一个包中扩展现有类型。你可以通过以下方式定义自己的类型,以原始类型为基础:

type MyRouter mux.Router

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

或者通过嵌入原始的路由器:

type MyRouter struct {
    *mux.Router
}

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

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

As the compiler mentions, you can't extend existing types in another package. You can define your own type backed by the original as follows:

type MyRouter mux.Router

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

or by embedding the original router:

type MyRouter struct {
    *mux.Router
}

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

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

答案2

得分: 201

我想对@jimt在这里给出的答案进行扩展。那个答案是正确的,并且在解决这个问题上给了我很大的帮助。然而,这两种方法(别名、嵌入)都有一些需要注意的地方。

注意:我使用父类和子类这些术语,尽管我不确定这是否适用于组合。基本上,父类是您想要在本地修改的类型。子类是试图实现该修改的新类型。

方法1 - 类型定义

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

方法2 - 嵌入(官方文档

type child struct {
    parent
}
// 或者使用导入和指针
type MyThing struct {
    *imported.Thing
}
  • 提供对字段的访问。
  • 提供对方法的访问。
  • 需要考虑初始化的问题。

总结

  • 使用组合方法时,如果嵌入的父类是指针,则不会初始化嵌入的父类。必须单独初始化父类。
  • 如果嵌入的父类是指针,并且在初始化子类时未初始化父类,将导致空指针解引用错误。
  • 类型定义和嵌入情况下都提供对父类字段的访问。
  • 类型定义不允许访问父类的方法,但嵌入父类可以。

您可以在以下代码中看到这一点。

在 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{}
    // 当父类是指针时,必须初始化它。
    // 否则,在尝试设置属性时会出现空指针错误。
    c3 := &childPointerParent{}
    c4 := &childPointerParent{&parent{}}

    c2.attr = "cObjParentAttr"
    // c3.attr = "cPointerParentAttr" // 不行,空指针解引用错误
    c4.attr = "cPointerParentAttr"

    // 可以这样做,因为我们继承了父类的字段
    fmt.Println(p.attr)
    fmt.Println(c1.attr)
    fmt.Println(c2.attr)
    fmt.Println(c4.attr)

    p.parentDo("在父类上调用 parentDo")
    c1.childAliasDo("在 ChildAlias 上调用 childAliasDo")
    c2.childObjParentDo("在 ChildObjParent 上调用 childObjParentDo")
    c3.childPointerParentDo("在 ChildPointerParent 上调用 childPointerParentDo")
    c4.childPointerParentDo("在 ChildPointerParent 上调用 childPointerParentDo")

    // 不能这样做,因为我们不继承父类的方法
    // c1.parentDo("在 childAlias 上调用 parentDo") // 不行,c1.parentDo 未定义

    // 可以这样做,因为我们继承了父类的方法
    c2.parentDo("在 childObjParent 上调用 parentDo")
    c3.parentDo("在 childPointerParent 上调用 parentDo")
    c4.parentDo("在 childPointerParent 上调用 parentDo")
}
英文:

I wanted to expand on the answer given by @jimt here. That answer is correct and helped me tremendously in sorting this out. However, there are some caveats to both methods (alias, embed) with which I had trouble.

note: I use the terms parent and child, though I'm not sure that is the best for composition. Basically, parent is the type which you want to modify locally. Child is the new type that attempts to implement that modification.

Method 1 - Type Definition

type child parent
// or
type MyThing imported.Thing
  • Provides access to the fields.
  • Does not provide access to the methods.

Method 2 - Embedding (official documentation)

type child struct {
	parent
}
// or with import and pointer
type MyThing struct {
	*imported.Thing
}
  • Provides access to the fields.
  • Provides access to the methods.
  • Requires consideration for initialization.

Summary

  • Using the composition method the embedded parent will not initialize if it is a pointer. The parent must be initialized separately.
  • If the embedded parent is a pointer and is not initialized when the child is initialized, a nil pointer dereference error will occur.
  • Both type definition and embed cases provide access to the fields of the parent.
  • The type definition does not allow access to the parent's methods, but embedding the parent does.

You can see this in the following code.

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")
}

答案3

得分: 3

扩展其他答案中的一个,我的情况下父类是一个数组。如果你想添加方法,但同时也要访问父类的方法,你必须在定义类型时进行包装,并在声明变量时进行包装:

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)
}
英文:

Expanding on one of the other answers, in my case the parent is an array. If you
want to add methods, but also have access to the parent methods, you must wrap
when defining the type, and wrap when declaring a variable:

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)
}

huangapple
  • 本文由 发表于 2015年3月2日 07:44:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/28800672.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定