Wie füge ich einem vorhandenen Typ in Go neue Methoden hinzu?

129

Ich möchte Route- gorilla/muxund Routertypen eine praktische Util-Methode hinzufügen :

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

aber der Compiler informiert mich

Neue Methoden für den nicht lokalen Typ mux.Router können nicht definiert werden

Wie würde ich das erreichen? Erstelle ich einen neuen Strukturtyp mit anonymen Feldern mux.Route und mux.Router? Oder etwas anderes?

Daniel Robinson
quelle
Interessanterweise werden Erweiterungsmethoden “extension methods are not object-oriented”für C # als nicht objektorientiert ( ) betrachtet, aber als ich sie heute betrachtete, wurde ich sofort an die Schnittstellen von Go (und ihren Ansatz zum Überdenken der Objektorientierung) erinnert, und dann hatte ich genau diese Frage.
Wolf

Antworten:

173

Wie der Compiler erwähnt, können Sie vorhandene Typen in einem anderen Paket nicht erweitern. Sie können Ihren eigenen Alias ​​oder Ihr eigenes Unterpaket wie folgt definieren:

type MyRouter mux.Router

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

oder durch Einbetten des Original-Routers:

type MyRouter struct {
    *mux.Router
}

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

...
r := &MyRouter{router}
r.F()
jimt
quelle
10
Oder einfach eine Funktion benutzen ...?
Paul Hankin
5
@ Paul dies zu tun ist erforderlich, um Funktionen wie String () und MarshalJSON ()
Riking
31
Wenn Sie den ersten Teil machen, wie zwingen Sie dann die mux.RouterInstanzen zu MyRouters? zB wenn Sie eine Bibliothek haben, die zurückgibt, mux.Routeraber Ihre neuen Methoden verwenden möchten?
docwhat
Wie benutzt man die erste Lösung? MyRouter (Router)
tfzxyinhao
Das Einbetten scheint etwas praktischer zu sein.
ivanjovanovic
124

Ich wollte die Antwort von @jimt hier erweitern . Diese Antwort ist richtig und hat mir enorm geholfen, dies zu klären. Es gibt jedoch einige Einschränkungen bei beiden Methoden (Alias, Einbettung), mit denen ich Probleme hatte.

Hinweis : Ich verwende die Begriffe Eltern und Kind, bin mir jedoch nicht sicher, ob dies für die Komposition am besten geeignet ist. Grundsätzlich ist Eltern der Typ, den Sie lokal ändern möchten. Child ist der neue Typ, der versucht, diese Änderung zu implementieren.

Methode 1 - Typdefinition

type child parent
// or
type MyThing imported.Thing
  • Bietet Zugriff auf die Felder.
  • Bietet keinen Zugriff auf die Methoden.

Methode 2 - Einbetten ( offizielle Dokumentation )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Bietet Zugriff auf die Felder.
  • Bietet Zugriff auf die Methoden.
  • Erfordert die Berücksichtigung für die Initialisierung.

Zusammenfassung

  • Bei Verwendung der Kompositionsmethode wird das eingebettete übergeordnete Element nicht initialisiert, wenn es sich um einen Zeiger handelt. Das übergeordnete Element muss separat initialisiert werden.
  • Wenn das eingebettete übergeordnete Element ein Zeiger ist und beim Initialisieren des untergeordneten Elements nicht initialisiert wird, tritt ein Nullzeiger-Dereferenzierungsfehler auf.
  • Sowohl Typdefinitions- als auch Einbettungsfälle bieten Zugriff auf die Felder des übergeordneten Elements.
  • Die Typdefinition ermöglicht keinen Zugriff auf die Methoden des übergeordneten Elements, das Einbetten des übergeordneten Elements jedoch.

Sie können dies im folgenden Code sehen.

Arbeitsbeispiel auf dem Spielplatz

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")
}
TheHerk
quelle
Ihr Beitrag ist sehr hilfreich, da Sie viel Forschung und Mühe zeigen, um zu versuchen, jede Technik Punkt für Punkt zu vergleichen. Lassen Sie mich Sie ermutigen, darüber nachzudenken, was bei der Konvertierung in eine bestimmte Benutzeroberfläche passiert. Ich meine, wenn Sie eine Struktur haben und diese Struktur möchten (von einem Drittanbieter, nehmen wir an), möchten Sie sich an eine bestimmte Schnittstelle anpassen. Wen schaffen Sie das? Sie können dafür Typalias oder Typeinbettung verwenden.
Victor
@ Victor Ich folge Ihrer Frage nicht, aber ich denke, Sie fragen, wie Sie eine Struktur erhalten, die Sie nicht kontrollieren, um eine bestimmte Schnittstelle zu erfüllen. Kurze Antwort, Sie tun es nur, indem Sie zu dieser Codebasis beitragen. Mit dem Material in diesem Beitrag können Sie jedoch von Anfang an eine andere Struktur erstellen und dann die Schnittstelle für diese Struktur implementieren. Siehe dieses Spielplatzbeispiel .
TheHerk
hi @TheHerk, ich möchte Sie auf einen weiteren Unterschied hinweisen, wenn Sie eine Struktur aus einem anderen Paket "erweitern". Mir scheint, dass es zwei Möglichkeiten gibt, dies zu archivieren, indem Sie den Typ-Alias ​​(Ihr Beispiel) und den Typ einbetten ( play.golang.org/p/psejeXYbz5T ) verwenden. Für mich sieht es so aus, als ob der Typalias die Konvertierung erleichtert, da Sie nur eine Typkonvertierung benötigen . Wenn Sie den Typumbruch verwenden, müssen Sie mithilfe eines Punkts auf die "übergeordnete" Struktur verweisen und so auf den übergeordneten Typ selbst zugreifen. Ich denke, es liegt
Victor
Bitte sehen Sie die Motivation dieses Themas hier stackoverflow.com/a/28800807/903998 , folgen Sie den Kommentaren und ich hoffe, Sie werden meinen Punkt sehen
Victor
Ich wünschte, ich könnte Ihrer Bedeutung folgen, aber ich habe immer noch Probleme. In der Antwort, auf die wir diese Kommentare schreiben, erkläre ich sowohl das Einbetten als auch das Aliasing, einschließlich der Vor- und Nachteile der einzelnen. Ich befürworte nicht eins über das andere. Es kann sein, dass Sie vorschlagen, ich hätte eines dieser Vor- oder Nachteile verpasst.
TheHerk