【问题标题】:How to add new methods to an existing type in Go?如何向 Go 中的现有类型添加新方法?
【发布时间】:2015-05-02 06:23:55
【问题描述】:

我想在gorilla/mux Route 和 Router 类型上添加一个方便的 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)
}

但编译器会通知我

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

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

【问题讨论】:

  • 有趣的是,扩展方法被认为是 C# 的非面向对象 (“extension methods are not object-oriented”),但今天看到它们时,我立即想起了 Go 的接口(以及它重新思考面向对象的方法),然后我就有了这个问题。

标签: go extension-methods


【解决方案1】:

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

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

【讨论】:

【解决方案2】:

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

注意:我使用“父母”和“孩子”这两个词,但我不确定这是否最适合组合。基本上, parent 是您要在本地修改的类型。 Child 是尝试实现该修改的新类型。

方法一——类型定义

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是一个指针并且在child初始化的时候没有初始化,会出现nil指针解引用错误。
  • 类型定义和嵌入案例都提供对父级字段的访问。
  • 类型定义不允许访问父级的方法,但嵌入父级可以。

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

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

【讨论】:

  • 您的帖子非常有帮助,因为它展示了大量的研究和努力,试图逐点比较每种技术。请允许我鼓励您思考在转换到给定界面方面会发生什么。我的意思是,如果你有一个结构并且你想要那个结构(假设来自第三方供应商)你想适应给定的接口,你会设法得到那个?您可以为此使用类型别名或类型嵌入。
  • @Victor 我不关注你的问题,但我认为你在问如何获得一个你无法控制的结构来满足给定的接口。简短的回答,除了为该代码库做出贡献外,您不会这样做。但是,使用本文中的材料,您可以从第一个结构创建另一个结构,然后在该结构上实现接口。看到这个playground example
  • 嗨@TheHerk,我的目标是让您指出从另一个包“扩展”结构时的另一个区别。在我看来,有两种方法可以存档,使用类型别名(您的示例)和使用类型嵌入(play.golang.org/p/psejeXYbz5T)。对我来说,类型别名看起来更容易转换,因为您只需要 类型转换,如果您使用类型包装,则需要使用点来引用“父”结构,因此,访问父类型本身。我猜这取决于客户端代码...
  • 对于方法1(类型定义),您仍然可以访问父方法。但是,您需要将别名(即子级)转换为父级。 c1 := &childAlias{"cAliasAttr"}ptr := (*parent)(c1)ptr.parentDo("called parentDo on ChildAlias")
  • @AdityaBasu 这是真的。您可以使用类型转换。在这种情况下有很多选择。
【解决方案3】:

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

type MyRouter mux.Router

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

或通过嵌入原始路由器:

type MyRouter struct {
    *mux.Router
}

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

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

【讨论】:

  • 或者只是使用一个函数...?
  • @Paul 这样做是覆盖 String() 和 MarshalJSON() 等函数所必需的
  • 如果您执行第一部分,那么您如何将mux.Router 实例强制为MyRouters?例如如果你有一个返回 mux.Router 的库,但你想使用你的新方法?
  • 第一种方案怎么用?我的路由器(路由器)
  • 嵌入似乎更实用一些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-14
  • 2010-12-18
  • 2019-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-23
相关资源
最近更新 更多