Goの既存の型に新しいメソッドを追加する方法は?


129

gorilla/muxRouteおよびRouterタイプに便利なutilメソッドを追加したいと思います。

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
または単に関数を使用します...?
Paul Hankin、2015年

5
これを行う@Paul文字列()とMarshalJSON()のようなオーバーライド機能に必要とされる
Riking

31
最初の部分を行う場合、mux.RouterインスタンスをどのようにMyRoutersに強制するのですか?たとえば、戻るライブラリがありmux.Router、新しいメソッドを使用したい場合は、
docwhat

最初のソリューションの使い方は?MyRouter(ルーター)
tfzxyinhao

埋め込みの方が少し実用的です。
ivanjovanovic 2017年

124

@jimtの回答をここで詳しく説明します。その答えは正解であり、これを整理するのに非常に役立ちました。ただし、両方の方法(エイリアス、埋め込み)にはいくつかの注意点があり、問題がありました。

:親と子という用語を使用していますが、それが作曲に最適かどうかはわかりません。基本的に、親はローカルで変更するタイプです。子は、その変更を実装しようとする新しいタイプです。

方法1-タイプの定義

type child parent
// or
type MyThing imported.Thing
  • フィールドへのアクセスを提供します。
  • メソッドへのアクセスを提供しません。

方法2-埋め込み(公式ドキュメント

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • フィールドへのアクセスを提供します。
  • メソッドへのアクセスを提供します。
  • 初期化には考慮が必要です。

概要

  • 組版メソッドを使用すると、埋め込まれた親がポインターの場合、初期化されません。親は個別に初期化する必要があります。
  • 埋め込まれた親がポインターであり、子が初期化されたときに初期化されていない場合、nilポインター逆参照エラーが発生します。
  • タイプ定義と埋め込みケースの両方で、親のフィールドにアクセスできます。
  • 型定義では、親のメソッドへのアクセスは許可されていませんが、親の埋め込みは許可されています。

これは次のコードで確認できます。

遊び場での作業例

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、2018

こんにちは@TheHerk、私が目指しているのは、別のパッケージから構造体を「拡張」するときに別の違いを指摘することです。これをアーカイブするには、タイプエイリアス(例)とタイプembed(play.golang.org/p/psejeXYbz5T)を使用する2つの方法があるように見えます。私にとっては、型変換が必要なだけなので、その型エイリアスが変換を容易にするように見えます。型ラップを使用する場合は、ドットを使用して「親」構造体を参照する必要があるため、親型自体にアクセスします。私はクライアントのコード次第だと思います...
ビクター

このトピックの動機をここで確認してください。stackoverflow.com/ a / 28800807/903998、コメントに従ってください。私のポイントが表示されることを願っています
Victor

あなたの意味を理解できたらいいのですが、それでも問題が解決しません。これらのコメントを書いている答えで、埋め込みとエイリアスの両方について、それぞれの長所と短所を含めて説明します。私はどちらか一方を擁護しているわけではありません。あなたが私がそれらの長所または短所の1つを逃したことを示唆している可能性があります。
TheHerk 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.