Jak dodać nowe metody do istniejącego typu w Go?

137

Chcę dodać wygodną metodę wykorzystania do gorilla/muxtypów tras i routerów:

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

ale kompilator informuje mnie

Nie można zdefiniować nowych metod w mux.Router typu nielokalnego

Więc jak mam to osiągnąć? Czy utworzę nowy typ struktury, który ma anonimowe pola mux.Route i mux.Router? Albo coś innego?

Daniel Robinson
źródło
Co ciekawe, metody rozszerzające są uważane za niezorientowane obiektowo ( “extension methods are not object-oriented”) dla C #, ale patrząc na nie dzisiaj, od razu przypomniałem sobie o interfejsach Go (i jego podejściu do ponownego przemyślenia orientacji obiektowej), a potem miałem to pytanie.
Wolf

Odpowiedzi:

180

Jak wspomina kompilator, nie można rozszerzyć istniejących typów w innym pakiecie. Możesz zdefiniować własny alias lub pakiet podrzędny w następujący sposób:

type MyRouter mux.Router

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

lub osadzając oryginalny router:

type MyRouter struct {
    *mux.Router
}

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

...
r := &MyRouter{router}
r.F()
jimt
źródło
10
Lub po prostu użyj funkcji ...?
Paul Hankin
5
@Paul robi to, aby zastąpić funkcje takie jak String () i MarshalJSON ()
Riking
36
Jeśli wykonasz pierwszą część, to w jaki sposób zmusisz mux.Routerwystąpienia do MyRouters? np. jeśli masz bibliotekę, która zwraca, mux.Routerale chcesz użyć nowych metod?
docwhat
jak skorzystać z pierwszego rozwiązania? MyRouter (router)
tfzxyinhao
osadzanie wydaje się nieco bardziej praktyczne w użyciu.
ivanjovanovic
135

Chciałem rozwinąć odpowiedź udzieloną przez @jimt tutaj . Ta odpowiedź jest poprawna i ogromnie pomogła mi w rozwiązaniu tego problemu. Istnieją jednak pewne zastrzeżenia dotyczące obu metod (alias, embed), z którymi miałem problem.

uwaga : używam terminów rodzic i dziecko, chociaż nie jestem pewien, czy jest to najlepsze dla kompozycji. Zasadniczo rodzic to typ, który chcesz zmodyfikować lokalnie. Dziecko to nowy typ, który próbuje zaimplementować tę modyfikację.

Metoda 1 - Definicja typu

type child parent
// or
type MyThing imported.Thing
  • Zapewnia dostęp do pól.
  • Nie zapewnia dostępu do metod.

Metoda 2 - Osadzanie ( oficjalna dokumentacja )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Zapewnia dostęp do pól.
  • Zapewnia dostęp do metod.
  • Wymaga rozważenia przy inicjalizacji.

Podsumowanie

  • Używając metody kompozycji, osadzony rodzic nie zainicjuje się, jeśli jest wskaźnikiem. Rodzica musi zostać zainicjowany oddzielnie.
  • Jeśli osadzony element nadrzędny jest wskaźnikiem i nie jest inicjowany podczas inicjowania elementu podrzędnego, wystąpi błąd wyłuskiwania wskaźnika zerowego.
  • Zarówno definicja typu, jak i przypadki osadzania zapewniają dostęp do pól elementu nadrzędnego.
  • Definicja typu nie pozwala na dostęp do metod rodzica, ale osadzenie rodzica tak.

Możesz to zobaczyć w poniższym kodzie.

przykład pracy na placu zabaw

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
źródło
Twój post jest bardzo pomocny, ponieważ pokazuje wiele badań i wysiłku, próbując porównać punkt po punkcie każdą technikę .. pozwól mi zachęcić Cię do przemyślenia tego, co się dzieje w kontekście konwersji do danego interfejsu. Chodzi mi o to, że jeśli masz strukturę i chcesz tę strukturę (załóżmy od zewnętrznego dostawcy), którą chcesz dostosować do danego interfejsu, komu uda ci się to zdobyć? Możesz użyć do tego aliasu typu lub typu embed.
Victor
@Victor Nie podążam za twoim pytaniem, ale myślę, że pytasz, jak uzyskać strukturę, której nie kontrolujesz, aby zadowolić dany interfejs. Krótka odpowiedź, nie musisz tylko pomagać w tworzeniu tej bazy kodu. Jednak korzystając z materiału w tym poście, możesz utworzyć inną strukturę z pierwszej, a następnie zaimplementować interfejs w tej strukturze. Zobacz ten przykład placu zabaw .
TheHerk
Cześć @TheHerk, Moim celem jest zwrócenie uwagi na inną różnicę przy „rozszerzaniu” struktury z innego pakietu. Wydaje mi się, że istnieją dwa sposoby na zrobienie tego, używając aliasu typu (Twój przykład) i używając typu embed ( play.golang.org/p/psejeXYbz5T ). Dla mnie wygląda na to, że alias typu ułatwia konwersję, ponieważ potrzebujesz tylko konwersji typu , jeśli używasz zawijania typów, musisz odwołać się do struktury „nadrzędnej” za pomocą kropki, uzyskując w ten sposób dostęp do samego typu nadrzędnego. Myślę, że zależy to od kodu klienta ...
Victor
Proszę zobaczyć motywację ten temat tutaj stackoverflow.com/a/28800807/903998 , śledzić komentarze i mam nadzieję, że będzie można zobaczyć mój punkt widzenia
Victor
Chciałabym podążać za twoim zamysłem, ale wciąż mam kłopoty. W odpowiedzi, na podstawie której piszemy te komentarze, wyjaśniam zarówno osadzanie, jak i aliasing, w tym zalety i wady każdego z nich. Nie opowiadam się za jednym nad drugim. Możliwe, że sugerujesz, że przegapiłem jedną z tych zalet lub wad.
TheHerk