Comment ajouter de nouvelles méthodes à un type existant dans Go?

129

Je souhaite ajouter une méthode utilitaire aux gorilla/muxtypes de routage et de routeur:

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

mais le compilateur m'informe

Impossible de définir de nouvelles méthodes sur un mux de type non local.

Alors, comment pourrais-je y parvenir? Est-ce que je crée un nouveau type de structure qui a des champs mux.Route et mux.Router anonymes? Ou autre chose?

Daniel Robinson
la source
Il est intéressant de noter que les méthodes d'extension sont considérées comme non orientées objet ( “extension methods are not object-oriented”) pour C #, mais en les regardant aujourd'hui, je me suis immédiatement souvenu des interfaces de Go (et de son approche pour repenser l'orientation des objets), puis j'ai eu cette question.
Wolf

Réponses:

174

Comme le compilateur le mentionne, vous ne pouvez pas étendre les types existants dans un autre package. Vous pouvez définir votre propre alias ou sous-package comme suit:

type MyRouter mux.Router

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

ou en intégrant le routeur d'origine:

type MyRouter struct {
    *mux.Router
}

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

...
r := &MyRouter{router}
r.F()
Jimt
la source
10
Ou simplement utiliser une fonction ...?
Paul Hankin
5
@Paul faisant cela est nécessaire pour remplacer des fonctions telles que String () et MarshalJSON ()
Riking
31
Si vous faites la première partie, comment contraignez-vous les mux.Routerinstances en MyRouters? par exemple si vous avez une bibliothèque qui retourne mux.Routermais que vous souhaitez utiliser vos nouvelles méthodes?
docwhat
comment utiliser la première solution? MyRouter (router)
tfzxyinhao
l'intégration semble un peu plus pratique à utiliser.
ivanjovanovic
124

Je voulais développer la réponse donnée par @jimt ici . Cette réponse est correcte et m'a énormément aidé à régler ce problème. Cependant, il y a quelques mises en garde concernant les deux méthodes (alias, incorporer) avec lesquelles j'ai eu des problèmes.

Remarque : j'utilise les termes parent et enfant, même si je ne suis pas sûr que ce soit le meilleur pour la composition. Fondamentalement, parent est le type que vous souhaitez modifier localement. Child est le nouveau type qui tente d'implémenter cette modification.

Méthode 1 - Définition du type

type child parent
// or
type MyThing imported.Thing
  • Donne accès aux champs.
  • Ne donne pas accès aux méthodes.

Méthode 2 - Intégration ( documentation officielle )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Donne accès aux champs.
  • Donne accès aux méthodes.
  • Nécessite une considération pour l'initialisation.

Résumé

  • En utilisant la méthode de composition, le parent incorporé ne s'initialise pas s'il s'agit d'un pointeur. Le parent doit être initialisé séparément.
  • Si le parent incorporé est un pointeur et n'est pas initialisé lorsque l'enfant est initialisé, une erreur de déréférence de pointeur nil se produit.
  • La définition de type et les cas incorporés donnent tous deux accès aux champs du parent.
  • La définition de type n'autorise pas l'accès aux méthodes du parent, contrairement à l'incorporation du parent.

Vous pouvez voir cela dans le code suivant.

exemple de travail sur l'aire de jeux

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
la source
votre message est très utile car il montre beaucoup de recherches et d'efforts essayant de comparer point par point chaque tecniche .. permettez-moi de vous encourager à réfléchir à ce qui se passe en termes de conversion vers une interface donnée. Je veux dire, si vous avez une structure et que vous voulez que cette structure (d'un fournisseur tiers, supposons) que vous vouliez vous adapter à une interface donnée, qui parvenez-vous à l'obtenir? Vous pouvez utiliser un alias de type ou un type incorporé pour cela.
Victor
@Victor Je ne suis pas votre question, mais je pense que vous demandez comment obtenir une structure que vous ne contrôlez pas pour satisfaire une interface donnée. Réponse courte, vous ne faites que contribuer à cette base de code. Cependant, en utilisant le matériel de cet article, vous pouvez créer une autre structure à partir du premier, puis implémenter l'interface sur cette structure. Voir cet exemple de terrain de jeu .
TheHerk
salut @TheHerk, Ce que je veux, c'est que vous signaliez une autre différence lorsque vous "étendez" une structure à partir d'un autre paquet. Il me semble qu'il y a deux façons d'archiver cela, en utilisant l'alias de type (votre exemple) et en utilisant le type embed ( play.golang.org/p/psejeXYbz5T ). Pour moi, il semble que cet alias de type facilite la conversion car vous n'avez besoin que d'une conversion de type , si vous utilisez le type wrap, vous devez référencer la structure "parent" en utilisant un point, accédant ainsi au type parent lui-même. Je suppose que cela dépend du code client ...
Victor
s'il vous plaît, voir la motivation de ce sujet ici stackoverflow.com/a/28800807/903998 , suivez les commentaires et j'espère que vous verrez mon point
Victor
J'aimerais pouvoir suivre votre sens, mais j'ai toujours des problèmes. Dans la réponse sur laquelle nous écrivons ces commentaires, j'explique à la fois l'intégration et l'aliasing, y compris les avantages et les inconvénients de chacun. Je ne préconise pas l'un plutôt que l'autre. Il se peut que vous suggériez que j'ai raté l'un de ces avantages ou inconvénients.
TheHerk