web-dev-qa-db-ja.com

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

しかし、コンパイラは私に通知します

非ローカルタイプmux.Routerで新しいメソッドを定義できません

それでは、どうすればこれを達成できますか?匿名のmux.Routeフィールドとmux.Routerフィールドを持つ新しい構造体タイプを作成しますか?または、他の何か?

96
Daniel Robinson

コンパイラが言及しているように、別のパッケージの既存の型を拡張することはできません。次のように、独自のエイリアスまたはサブパッケージを定義できます。

type MyRouter mux.Router

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

または元のルーターを埋め込むことにより:

type MyRouter struct {
    *mux.Router
}

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

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

@jimt here で与えられる答えを拡張したかった。その答えは正解であり、これを整理するのに非常に役立ちました。ただし、両方の方法(エイリアス、埋め込み)には注意が必要な点があります。

note:親と子という用語を使用しますが、それが作曲に最適かどうかはわかりません。基本的に、親はローカルで変更するタイプです。子は、その変更を実装しようとする新しいタイプです。

方法1-タイプ定義

type child parent
// or
type MyThing imported.Thing
  • フィールドへのアクセスを提供します。
  • メソッドへのアクセスを提供しません。

方法2-埋め込み( 公式ドキュメント

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • フィールドへのアクセスを提供します。
  • メソッドへのアクセスを提供します。
  • 初期化の検討が必要です。

概要

  • 合成メソッドを使用すると、埋め込み親はポインターの場合は初期化されません。親は個別に初期化する必要があります。
  • 埋め込まれた親がポインターであり、子が初期化されるときに初期化されない場合、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")
}
92
TheHerk