Bagaimana cara menambahkan metode baru ke jenis yang ada di Go?

129

Saya ingin menambahkan metode util kenyamanan ke gorilla/muxtipe Route dan 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)
}

tetapi kompiler memberitahu saya

Tidak dapat menentukan metode baru pada mux.Router tipe non-lokal

Jadi bagaimana saya mencapai ini? Apakah saya membuat tipe struct baru yang memiliki bidang mux.Rout anonim dan mux.Router? Atau sesuatu yang lain?

Daniel Robinson
sumber
Menariknya metode ekstensi dianggap sebagai non-object-oriented ( “extension methods are not object-oriented”) untuk C #, tetapi ketika melihatnya hari ini, saya langsung teringat antarmuka Go (dan pendekatannya untuk memikirkan kembali orientasi objek), dan kemudian saya punya pertanyaan yang sangat mirip ini.
Wolf

Jawaban:

174

Seperti yang disebutkan oleh kompiler, Anda tidak dapat memperluas tipe yang ada di paket lain. Anda dapat mendefinisikan alias atau sub paket Anda sendiri sebagai berikut:

type MyRouter mux.Router

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

atau dengan menyematkan router asli:

type MyRouter struct {
    *mux.Router
}

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

...
r := &MyRouter{router}
r.F()
Jim
sumber
10
Atau cukup gunakan fungsi ...?
Paul Hankin
5
@ Paul melakukan ini diperlukan untuk mengesampingkan fungsi-fungsi seperti String () dan MarshalJSON ()
Riking
31
Jika Anda melakukan bagian pertama, lalu bagaimana Anda memaksa mux.Routerinstances ke MyRouters? mis. jika Anda memiliki perpustakaan yang mengembalikan mux.Routertetapi Anda ingin menggunakan metode baru Anda?
docwhat
bagaimana cara menggunakan solusi pertama? MyRouter (router)
tfzxyinhao
menanamkan tampaknya sedikit lebih praktis untuk digunakan.
ivanjovanovic
124

Saya ingin memperluas jawaban yang diberikan oleh @jimt di sini . Jawaban itu benar dan sangat membantu saya dalam menyelesaikan masalah ini. Namun, ada beberapa peringatan untuk kedua metode (alias, sematan) yang saya punya masalah.

Catatan : Saya menggunakan istilah orangtua dan anak, meskipun saya tidak yakin itu yang terbaik untuk komposisi. Pada dasarnya, parent adalah tipe yang ingin Anda modifikasi secara lokal. Child adalah tipe baru yang mencoba mengimplementasikan modifikasi itu.

Metode 1 - Jenis Definisi

type child parent
// or
type MyThing imported.Thing
  • Memberikan akses ke bidang.
  • Tidak menyediakan akses ke metode.

Metode 2 - Penanaman ( dokumentasi resmi )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Memberikan akses ke bidang.
  • Memberikan akses ke metode.
  • Membutuhkan pertimbangan untuk inisialisasi.

Ringkasan

  • Menggunakan metode komposisi, induk yang disematkan tidak akan menginisialisasi jika itu adalah sebuah pointer. Induk harus diinisialisasi secara terpisah.
  • Jika orang tua yang disematkan adalah pointer dan tidak diinisialisasi ketika anak diinisialisasi, kesalahan nereferensi pointer akan terjadi.
  • Definisi tipe dan case embed menyediakan akses ke bidang induk.
  • Definisi tipe tidak memungkinkan akses ke metode induk, tetapi menanamkan induk tidak.

Anda dapat melihat ini dalam kode berikut.

contoh kerja di taman bermain

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
sumber
posting Anda sangat membantu karena menunjukkan banyak penelitian dan upaya mencoba untuk membandingkan poin demi poin setiap tecniche .. izinkan saya untuk mendorong Anda untuk berpikir apa yang terjadi dalam hal konversi ke antarmuka yang diberikan. Maksud saya, jika Anda memiliki struct dan Anda ingin struct itu (dari vendor pihak ketiga mari kita asumsikan) Anda ingin beradaptasi dengan antarmuka yang diberikan, siapa yang Anda kelola untuk mendapatkannya? Anda dapat menggunakan jenis alias atau jenis sematan untuk itu.
Victor
@ Viktor Saya tidak mengikuti pertanyaan Anda, tapi saya pikir Anda bertanya bagaimana cara mendapatkan struct yang tidak Anda kontrol untuk memenuhi antarmuka yang diberikan. Jawaban singkat, Anda tidak kecuali dengan berkontribusi pada basis kode itu. Namun, menggunakan materi dalam posting ini, Anda bisa membuat struct lain dari yang pertama, lalu mengimplementasikan antarmuka pada struct itu. Lihat contoh taman bermain ini .
TheHerk
hi @ TheHerk, Yang saya maksud adalah Anda menunjukkan perbedaan lain ketika "memperluas" sebuah struct dari paket lain. Sepertinya bagi saya, bahwa ada dua cara untuk mengaktifkan ini, dengan menggunakan tipe alias (contoh Anda) dan menggunakan tipe embed ( play.golang.org/p/psejeXYbz5T ). Bagi saya sepertinya tipe alias itu membuat konversi lebih mudah karena Anda hanya perlu konversi tipe , jika Anda menggunakan tipe wrap, Anda perlu mereferensikan struct "parent" dengan menggunakan sebuah titik, dengan demikian, mengakses tipe induk itu sendiri. Saya kira terserah kode klien ...
Victor
tolong, lihat motivasi dari topik ini di sini stackoverflow.com/a/28800807/903998 , ikuti komentarnya dan saya harap Anda akan mengerti maksud saya
Victor
Saya berharap saya bisa mengikuti maksud Anda, tetapi saya masih mengalami kesulitan. Dalam jawaban yang kami tuliskan komentar-komentar ini, saya menjelaskan baik embedding dan aliasing, termasuk kelebihan dan kekurangan masing-masing. Saya tidak menganjurkan satu untuk yang lain. Mungkin Anda menyarankan saya melewatkan salah satu pro atau kontra itu.
TheHerk