¿Cómo agregar nuevos métodos a un tipo existente en Go?

129

Quiero agregar un método de utilidad conveniente a los gorilla/muxtipos de ruta y enrutador:

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

pero el compilador me informa

No se pueden definir nuevos métodos en mux de tipo no local.

Entonces, ¿cómo podría lograr esto? ¿Creo un nuevo tipo de estructura que tiene un campo anónimo mux.Route y mux.Router? ¿O algo mas?

Daniel Robinson
fuente
Curiosamente, los métodos de extensión se consideran no orientados a objetos ( “extension methods are not object-oriented”) para C #, pero al mirarlos hoy, inmediatamente me acordé de las interfaces de Go (y su enfoque para repensar la orientación a objetos), y luego tuve esta misma pregunta.
Wolf

Respuestas:

174

Como menciona el compilador, no puede extender los tipos existentes en otro paquete. Puede definir su propio alias o subpaquete de la siguiente manera:

type MyRouter mux.Router

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

o incrustando el enrutador original:

type MyRouter struct {
    *mux.Router
}

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

...
r := &MyRouter{router}
r.F()
jimt
fuente
10
¿O simplemente usa una función ...?
Paul Hankin
55
@Paul hace esto para anular funciones como String () y MarshalJSON ()
Riking
31
Si hace la primera parte, ¿cómo coacciona las mux.Routerinstancias a MyRouters? por ejemplo, si tiene una biblioteca que regresa mux.Routerpero quiere usar sus nuevos métodos?
docwhat
¿Cómo usar la primera solución? MyRouter (enrutador)
tfzxyinhao
la inserción parece un poco más práctica de usar.
ivanjovanovic
124

Quería ampliar la respuesta dada por @jimt aquí . Esa respuesta es correcta y me ayudó enormemente a resolver esto. Sin embargo, hay algunas advertencias a ambos métodos (alias, incrustar) con las que tuve problemas.

nota : uso los términos padre e hijo, aunque no estoy seguro de que sea el mejor para la composición. Básicamente, padre es el tipo que desea modificar localmente. Hijo es el nuevo tipo que intenta implementar esa modificación.

Método 1 - Definición de tipo

type child parent
// or
type MyThing imported.Thing
  • Proporciona acceso a los campos.
  • No proporciona acceso a los métodos.

Método 2: incrustación ( documentación oficial )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Proporciona acceso a los campos.
  • Proporciona acceso a los métodos.
  • Requiere consideración para la inicialización.

Resumen

  • Usando el método de composición, el padre incrustado no se inicializará si es un puntero. El padre debe inicializarse por separado.
  • Si el padre incrustado es un puntero y no se inicializa cuando se inicializa el niño, se producirá un error de desreferencia de puntero nulo.
  • Tanto la definición de tipo como los casos de inserción proporcionan acceso a los campos del padre.
  • La definición de tipo no permite el acceso a los métodos del padre, pero la incorporación del padre sí.

Puedes ver esto en el siguiente código.

ejemplo de trabajo en el patio de recreo

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
fuente
su publicación es muy útil ya que muestra mucha investigación y esfuerzo tratando de comparar punto por punto cada tecniche .. permítame animarlo a pensar qué sucede en términos de conversión a una interfaz determinada. Quiero decir, si tienes una estructura y quieres esa estructura (de un proveedor externo, supongamos) que quieres adaptar a una interfaz determinada, ¿a quién consigues conseguirla? Podría emplear el alias de tipo o el tipo de inserción para eso.
Victor
@Victor No sigo tu pregunta, pero creo que estás preguntando cómo obtener una estructura que no controlas para satisfacer una interfaz determinada. Respuesta corta, no lo hace excepto contribuyendo a esa base de código. Sin embargo, utilizando el material de esta publicación, puede crear otra estructura a partir de la primera, luego implementar la interfaz en esa estructura. Vea este ejemplo de patio de recreo .
TheHerk
hola @TheHerk, mi objetivo es que destaques otra diferencia cuando "extiendas" una estructura desde otro paquete. A mí me parece que hay dos formas de archivar esto, usando el alias de tipo (su ejemplo) y usando el tipo de inserción ( play.golang.org/p/psejeXYbz5T ). Para mí, parece que ese alias de tipo facilita la conversión, ya que solo necesita una conversión de tipo , si usa type wrap necesita hacer referencia a la estructura "parent" utilizando un punto, por lo tanto, acceder al tipo padre en sí. Supongo que depende del código del cliente ...
Victor
por favor, vea la motivación de este tema aquí stackoverflow.com/a/28800807/903998 , siga los comentarios y espero que vea mi punto
Victor
Desearía poder seguir tu significado, pero sigo teniendo problemas. En la respuesta sobre la que estamos escribiendo estos comentarios, explico tanto la incrustación como el alias, incluidas las ventajas y desventajas de cada uno. No estoy abogando por uno sobre el otro. Puede ser que sugieras que me perdí uno de esos pros o contras.
TheHerk