Як додати нові методи до наявного типу в Go?


129

Я хочу додати зручний метод утиліти до gorilla/muxтипів маршрутів та маршрутизаторів:

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

але компілятор повідомляє мене

Неможливо визначити нові методи на mux.Router немісцевого типу

То як би я досяг цього? Чи потрібно створити новий тип структури з анонімними полями mux.Route та mux.Router? Або щось інше?


Цікаво, що методи розширення розглядаються як не об’єктно-орієнтовані ( “extension methods are not object-oriented”) для C #, але, дивлячись на них сьогодні, мені одразу запам'яталися інтерфейси Go (та його підхід до переосмислення об'єктної орієнтації), і тоді у мене виникло саме це питання.
Вовк

Відповіді:


173

Як згадує компілятор, ви не можете розширити існуючі типи в іншому пакеті. Ви можете визначити власний псевдонім або підпакет таким чином:

type MyRouter mux.Router

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

або вставивши оригінальний маршрутизатор:

type MyRouter struct {
    *mux.Router
}

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

...
r := &MyRouter{router}
r.F()

10
Або просто використовувати функцію ...?
Пол Ханкін

5
@Paul це потрібно, щоб змінити такі функції, як String () і MarshalJSON ()
Riking

31
Якщо ви виконаєте першу частину, то як ви примушуєте mux.Routerекземпляри до MyRouters? наприклад, якщо у вас є бібліотека, яка повертається, mux.Routerале ви хочете використовувати свої нові методи?
док

як використовувати перше рішення? MyRouter (router)
tfzxyinhao

вбудова здається трохи практичнішим у використанні.
ivanjovanovic

124

Я хотів розкрити відповідь, надану тут @jimt . Ця відповідь правильна і дуже допомогла мені розібратися в цьому. Однак існують деякі застереження до обох методів (псевдонім, вбудовування), з якими у мене виникли проблеми.

Примітка : я використовую терміни батько і дитина, хоча я не впевнений, що це найкраще для композиції. В основному, батьківський - це тип, який потрібно змінити локально. Child - це новий тип, який намагається реалізувати цю модифікацію.

Метод 1 - Визначення типу

type child parent
// or
type MyThing imported.Thing
  • Забезпечує доступ до полів.
  • Не забезпечує доступ до методів.

Спосіб 2 - Вставлення ( офіційна документація )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Забезпечує доступ до полів.
  • Забезпечує доступ до методів.
  • Потрібен розгляд для ініціалізації.

Підсумок

  • Використовуючи метод композиції, вбудований батько не ініціалізується, якщо він є вказівником. Батьківщину необхідно ініціалізувати окремо.
  • Якщо вбудований батько є вказівником і не ініціалізується при ініціалізації дочірнього пристрою, станеться помилка перезапису нуля.
  • І випадки визначення, і випадки вбудовування забезпечують доступ до полів батьків.
  • Визначення типу не дозволяє отримати доступ до батьківських методів, але вбудовування батьків робить.

Ви можете бачити це в наступному коді.

робочий приклад на дитячому майданчику

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

Ваша публікація дуже корисна, тому що показуйте багато досліджень та зусиль, намагаючись порівняти по пунктах кожну техніку. Дозвольте мені спонукати вас думати, що відбувається з точки зору перетворення на заданий інтерфейс. Я маю на увазі, якщо у вас є структура, і ви хочете, щоб цю структуру (від стороннього постачальника припустимо) ви хочете адаптувати до заданого інтерфейсу, кому вам це вдається отримати? Ви можете використати псевдонім типу або вбудувати для цього.
Віктор

@Victor Я не дотримуюся вашого запитання, але я думаю, що ви запитуєте, як отримати структуру, яку ви не контролюєте, щоб задовольнити заданий інтерфейс. Коротку відповідь ви не маєте, окрім того, зробивши внесок у цю кодову базу. Однак, використовуючи матеріал у цій публікації, ви можете створити іншу структуру з першої, потім реалізувати інтерфейс для цієї структури. Дивіться приклад цього ігрового майданчика .
TheHerk

привіт @TheHerk, що я маю на меті вказати на іншу різницю при "розширенні" структури з іншого пакету. Мені здається, що є два способи архевізувати це, використовуючи псевдонім типу (ваш приклад) та використовуючи тип embed ( play.golang.org/p/psejeXYbz5T ). Для мене це виглядає так, що псевдонім типу полегшує перетворення, оскільки вам потрібна лише конверсія типу , якщо ви використовуєте обгортку типу, вам потрібно посилатись на "батьківську" структуру за допомогою крапки, таким чином, отримуючи доступ до самого батьківського типу. Напевно, залежить від коду клієнта ...
Віктор

будь ласка, дивіться мотивацію цієї теми тут stackoverflow.com/a/28800807/903998 , слідкуйте за коментарями, і я сподіваюся, що ви побачите мою думку
Віктор

Я б хотів, щоб я міг слідувати вашому змісту, але у мене все ще виникають проблеми. У відповіді, на яку ми пишемо ці коментарі, я пояснюю як вбудовування, так і згладжування, включаючи переваги та недоліки кожного. Я не виступаю за одне над іншим. Можливо, ви говорите про те, що я пропустив один із цих плюсів чи мінусів.
TheHerk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.