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


129

我想在gorilla/muxRoute和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字段的新结构类型?或者是其他东西?


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

Answers:


173

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

type MyRouter mux.Router

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

或嵌入原始路由器:

type MyRouter struct {
    *mux.Router
}

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

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

10
或者只是使用一个功能...?
保罗·汉金

5
@Paul必须这样做,才能覆盖String()和MarshalJSON()之类的功能
2016年

31
如果执行第一部分,那么如何将mux.Router实例强制为MyRouters?例如,如果您有一个返回的库,mux.Router但您想使用新方法?
docwhat

如何使用第一个解决方案?MyRouter(router)
tfzxyinhao

嵌入似乎更实用。
ivanjovanovic

124

我想通过@jimt给出的答案展开这里。这个答案是正确的,并在很大程度上帮助了我。但是,这两种方法(别名,嵌入)都存在一些警告,但我遇到了麻烦。

注意:我不确定使用“父母”和“孩子”这两个术语,尽管我不确定这对构图是最好的。基本上,parent是您要在本地修改的类型。Child是尝试实现该修改的新类型。

方法1-类型定义

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

方法2-嵌入(官方文档

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

摘要

  • 使用composition方法,如果嵌入式父对象是指针,则不会对其进行初始化。父项必须单独初始化。
  • 如果嵌入的父对象是一个指针,并且在初始化子对象时未初始化,则将发生nil指针取消引用错误。
  • 类型定义案例和嵌入案例都提供对父级字段的访问。
  • 类型定义不允许访问父级的方法,但嵌入父级则可以。

您可以在以下代码中看到它。

在操场上的工作例子

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我没有回答您的问题,但是我认为您在问如何获得不受控制的结构以满足给定的接口。简短的答案,除了为该代码库做贡献之外,您没有别的。但是,使用本文中的材料,您可以从第一个创建另一个结构,然后在该结构上实现接口。请参阅此操场示例
TheHerk

@TheHerk,您好,我的目的是让您指出从另一个程序包“扩展”结构时的另一个区别。在我看来,通过使用类型别名(您的示例)和使用类型嵌入(play.golang.org/p/psejeXYbz5T),可以通过两种方式对此进行归档。对我来说,类型别名使转换更容易,因为您只需要类型转换,如果使用类型包装,则需要使用点引用“父”结构,从而访问父类型本身。我想这取决于客户端代码...
Victor,

请在这里stackoverflow.com/a/28800807/903998看到该主题的动机,关注评论,我希望您能明白我的意思
Victor

希望我能听懂您的意思,但仍然遇到麻烦。在我们写这些评论的答案中,我解释了嵌入和别名,包括它们的优缺点。我不是在主张一个。可能是您在暗示我错过了这些优点或缺点之一。
TheHerk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.