Como adicionar novos métodos a um tipo existente no Go?

129

Quero adicionar um método utilitário de conveniência aos gorilla/muxtipos de roteador e roteador:

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

mas o compilador me informa

Não é possível definir novos métodos no mux do tipo não local.

Então, como eu conseguiria isso? Crio um novo tipo de estrutura que possui um campo anônimo mux.Route e mux.Router? Ou alguma outra coisa?

Daniel Robinson
fonte
Curiosamente, os métodos de extensão são considerados como não orientados a objetos ( “extension methods are not object-oriented”) para C #, mas, ao analisá-los hoje, fui imediatamente lembrado das interfaces do Go (e sua abordagem para repensar a orientação a objetos), e então tive essa pergunta.
Wolf

Respostas:

173

Como o compilador menciona, você não pode estender os tipos existentes em outro pacote. Você pode definir seu próprio alias ou subpacote da seguinte maneira:

type MyRouter mux.Router

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

ou incorporando o roteador original:

type MyRouter struct {
    *mux.Router
}

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

...
r := &MyRouter{router}
r.F()
jimt
fonte
10
Ou apenas use uma função ...?
Paul Hankin
5
@ Paulo fazendo isso é necessário para funções de substituição como String () e MarshalJSON ()
Riking
31
Se você faz a primeira parte, como coagir as mux.Routerinstâncias a MyRouters? por exemplo, se você tem uma biblioteca que retorna, mux.Routermas deseja usar seus novos métodos?
docwhat
como usar a primeira solução? MyRouter (roteador)
tfzxyinhao
a incorporação parece um pouco mais prática de usar.
Ivanjovanovic 30/01
124

Eu queria expandir a resposta dada por @jimt aqui . Essa resposta está correta e me ajudou tremendamente a resolver isso. No entanto, existem algumas ressalvas nos dois métodos (alias, incorporar) com os quais tive problemas.

nota : eu uso os termos pai e filho, embora não tenha certeza de que seja o melhor para composição. Basicamente, pai é o tipo que você deseja modificar localmente. Filho é o novo tipo que tenta implementar essa modificação.

Método 1 - Definição de tipo

type child parent
// or
type MyThing imported.Thing
  • Fornece acesso aos campos.
  • Não fornece acesso aos métodos.

Método 2 - Incorporação ( documentação oficial )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Fornece acesso aos campos.
  • Fornece acesso aos métodos.
  • Requer consideração para inicialização.

Resumo

  • Usando o método de composição, o pai incorporado não será inicializado se for um ponteiro. O pai deve ser inicializado separadamente.
  • Se o pai incorporado for um ponteiro e não for inicializado quando o filho for inicializado, ocorrerá um erro de desreferência de ponteiro nulo.
  • Os casos de definição de tipo e incorporação fornecem acesso aos campos do pai.
  • A definição de tipo não permite acesso aos métodos do pai, mas a incorporação do pai o faz.

Você pode ver isso no código a seguir.

exemplo de trabalho no parquinho

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
fonte
seu post é muito útil, pois mostra muita pesquisa e esforço tentando comparar ponto por ponto cada técnica. permita-me incentivá-lo a pensar no que acontece em termos de conversão para uma determinada interface. Quero dizer, se você tem uma estrutura e deseja essa estrutura (de um fornecedor de terceiros, vamos assumir) que deseja se adaptar a uma determinada interface, quem você consegue obter? Você pode empregar alias de tipo ou incorporar para isso.
Victor Victor
@ Victor Eu não sigo sua pergunta, mas acho que você está perguntando como obter uma estrutura que você não controla para satisfazer uma determinada interface. Resposta curta, você não, exceto contribuindo para essa base de código. No entanto, usando o material nesta postagem, você pode criar outra estrutura a partir da primeira e, em seguida, implementar a interface nessa estrutura. Veja este exemplo de playground .
TheHerk
oi @ TheHerk, O meu objetivo é que você aponte outra diferença ao "estender" uma estrutura de outro pacote. Parece-me que existem duas maneiras de obter isso, usando o alias do tipo (seu exemplo) e o tipo embed ( play.golang.org/p/psejeXYbz5T ). Para mim, parece que esse alias de tipo facilita a conversão, pois você só precisa de uma conversão de tipo . Se você usar o tipo wrap, precisará referenciar a estrutura "pai" usando um ponto, acessando o próprio tipo pai. Eu acho que é até o código do cliente ...
Victor
por favor, veja a motivação deste tópico aqui stackoverflow.com/a/28800807/903998 , siga os comentários e eu espero que você vai ver o meu ponto
Victor
Eu gostaria de poder seguir o seu significado, mas ainda estou tendo problemas. Na resposta sobre a qual estamos escrevendo esses comentários, explico a incorporação e o alias, incluindo as vantagens e desvantagens de cada um. Não estou defendendo um sobre o outro. Pode ser que você esteja sugerindo que eu perdi um desses prós ou contras.
TheHerk