Come aggiungere nuovi metodi a un tipo esistente in Go?


129

Voglio aggiungere un metodo util convenienza su gorilla/muxtipi di route e router:

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

ma il compilatore mi informa

Impossibile definire nuovi metodi su tipo non locale mux.Router

Quindi come potrei raggiungere questo obiettivo? Posso creare un nuovo tipo di struttura con campi anonimi mux.Route e mux.Router? O qualcos'altro?


È interessante notare che i metodi di estensione sono considerati non orientati agli oggetti ( “extension methods are not object-oriented”) per C #, ma quando li guardo oggi, mi sono subito ricordato delle interfacce di Go (e del suo approccio per ripensare l'orientamento degli oggetti), e quindi ho avuto proprio questa domanda.
Lupo,

Risposte:


174

Come menzionato dal compilatore, non è possibile estendere i tipi esistenti in un altro pacchetto. È possibile definire il proprio alias o pacchetto secondario come segue:

type MyRouter mux.Router

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

o incorporando il router originale:

type MyRouter struct {
    *mux.Router
}

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

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

10
O semplicemente usa una funzione ...?
Paul Hankin,

5
@Paul è necessario per sovrascrivere funzioni come String () e MarshalJSON ()
Riking

31
Se fai la prima parte, allora come costringi le mux.Routeristanze a MyRouters? ad esempio se hai una libreria che ritorna mux.Routerma vuoi usare i tuoi nuovi metodi?
docwhat il

come usare la prima soluzione? MyRouter (router)
tfzxyinhao

l'incorporamento sembra un po 'più pratico da usare.
Ivanjovanovic,

124

Volevo ampliare la risposta data da @jimt qui . Questa risposta è corretta e mi ha aiutato moltissimo a risolvere questo problema. Tuttavia, ci sono alcuni avvertimenti su entrambi i metodi (alias, embed) con i quali ho avuto problemi.

nota : uso i termini genitore e figlio, anche se non sono sicuro che sia il migliore per composizione. Fondamentalmente, parent è il tipo che si desidera modificare localmente. Child è il nuovo tipo che tenta di implementare quella modifica.

Metodo 1 - Digitare la definizione

type child parent
// or
type MyThing imported.Thing
  • Fornisce accesso ai campi.
  • Non fornisce accesso ai metodi.

Metodo 2 - Incorporamento ( documentazione ufficiale )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Fornisce accesso ai campi.
  • Fornisce accesso ai metodi.
  • Richiede considerazione per l'inizializzazione.

Sommario

  • Utilizzando il metodo di composizione, il genitore incorporato non verrà inizializzato se si tratta di un puntatore. Il genitore deve essere inizializzato separatamente.
  • Se il genitore incorporato è un puntatore e non è inizializzato quando il figlio è inizializzato, si verificherà un errore di dereference del puntatore zero.
  • Sia la definizione del tipo che i casi di incorporamento forniscono accesso ai campi del genitore.
  • La definizione del tipo non consente l'accesso ai metodi del genitore, ma lo incorpora il genitore.

Puoi vederlo nel seguente codice.

esempio funzionante nel parco giochi

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

il tuo post è molto utile in quanto mostra molte ricerche e sforzi nel cercare di confrontare punto per punto ogni tecnica .. permettimi di incoraggiarti a pensare a ciò che accade in termini di conversione in una determinata interfaccia. Voglio dire, se hai una struttura e vuoi quella struttura (supponiamo da un fornitore di terze parti) vuoi adattarti a una data interfaccia, chi riesci a ottenere? È possibile utilizzare l'alias di tipo o il tipo di incorporamento per questo.
Victor,

@Victor Non seguo la tua domanda, ma penso che tu stia chiedendo come ottenere una struttura che non controlli per soddisfare una determinata interfaccia. Risposta breve, non lo fai se non contribuendo a quella base di codice. Tuttavia, utilizzando il materiale in questo post, è possibile creare un'altra struttura dalla prima, quindi implementare l'interfaccia su quella struttura. Vedi questo esempio di parco giochi .
TheHerk,

ciao @TheHerk, il mio obiettivo è farti notare un'altra differenza quando "estendi" una struttura da un altro pacchetto. Mi sembra che ci siano due modi per archeologare questo, usando il tipo alias (il tuo esempio) e usando il tipo embed ( play.golang.org/p/psejeXYbz5T ). A mio avviso, questo alias di tipo semplifica la conversione in quanto è necessaria solo una conversione di tipo , se si utilizza la disposizione del tipo è necessario fare riferimento alla struttura "parent" utilizzando un punto, quindi accedendo al tipo parent stesso. Suppongo dipenda dal codice cliente ...
Victor,

per favore, vedi la motivazione di questo argomento qui stackoverflow.com/a/28800807/903998 , segui i commenti e spero che vedrai il mio punto
Victor

Vorrei poter seguire il tuo significato, ma ho ancora problemi. Nella risposta su cui stiamo scrivendo questi commenti, spiego sia l'incorporamento che l'aliasing, compresi i vantaggi e gli svantaggi di ciascuno. Non sto sostenendo l'uno sull'altro. Può darsi che tu stia suggerendo che ho perso uno di quei pro o contro.
TheHerk
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.