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에서 새 메소드를 정의 할 수 없습니다.

어떻게 이것을 달성 할 수 있습니까? 익명의 mux.Route 및 mux.Router 필드가있는 새 구조체 유형을 작성합니까? 또는 다른 것?


흥미롭게도 확장 방법은 “extension methods are not object-oriented”C #의 비 객체 지향 ( ) 으로 간주 되지만 오늘 살펴볼 때 Go의 인터페이스 (및 객체 방향을 다시 생각하는 접근 방식)를 즉시 기억하고 나서이 질문이 있습니다.
Wolf

답변:


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

5
@Paul은 String () 및 MarshalJSON ()과 같은 함수를 재정의하는 데 필요합니다.
Riking

31
첫 번째 부분을 수행하면 mux.Router인스턴스를 MyRouters로 어떻게 강제 합니까? 예를 들어 라이브러리가 반환 mux.Router되지만 새 메서드를 사용하려는 경우?
docwhat

첫 번째 솔루션을 사용하는 방법? MyRouter (router)
tfzxyinhao 2016 년

임베딩은 좀 더 실용적으로 보입니다.
ivanjovanovic

124

@jimt가 제공 한 답변을 확장하고 싶었 습니다 . 그 대답은 정확하고 이것을 분류하는 데 엄청나게 도움이되었습니다. 그러나 문제가있는 두 가지 방법 (별명, 포함)에주의해야 할 점이 있습니다.

참고 : 나는 부모와 자식이라는 용어를 사용하지만 그것이 구성에 가장 적합한 지 확실하지 않습니다. 기본적으로 parent는 로컬에서 수정하려는 유형입니다. 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
}
  • 필드에 대한 액세스를 제공합니다.
  • 메소드에 대한 액세스를 제공합니다.
  • 초기화를 고려해야합니다.

요약

  • 작성 방법을 사용하면 포함 된 상위가 포인터 인 경우 초기화되지 않습니다. 부모는 별도로 초기화해야합니다.
  • 포함 된 부모가 포인터이고 자식이 초기화 될 때 초기화되지 않은 경우 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")
}

귀하의 게시물은 각 기술별로 포인트를 비교하려고 많은 연구와 노력을 보여 주므로 매우 도움이됩니다. 주어진 인터페이스로의 전환 측면에서 발생하는 일을 생각하도록 장려 할 수 있습니다. 내 말은, 당신이 구조체를 가지고 있고 그 구조체 (제 3 자 공급 업체의 가정)가 주어진 인터페이스에 적응하기를 원한다면, 누가 그것을 얻을 수 있습니까? 이를 위해 유형 별명 또는 유형 임베드를 사용할 수 있습니다.
Victor

@ 빅터 나는 당신의 질문을 따르지 않지만 주어진 인터페이스를 만족시키기 위해 제어하지 않는 구조체를 얻는 방법을 묻는 것 같아요. 짧은 대답, 당신은 그 코드베이스에 기여하는 것을 제외하고는 아닙니다. 그러나이 포스트의 머티리얼을 사용하여 첫 번째 구조체에서 다른 구조체를 만든 다음 해당 구조체에 인터페이스를 구현할 수 있습니다. 이 운동장 예를 참조하십시오 .
TheHerk

안녕 @TheHerk, 내가 목표로하는 것은 다른 패키지에서 구조체를 "확장"할 때 또 다른 차이점을 지적하는 것입니다. 나에게 보이는 것처럼, 타입 별칭 (예 :)을 사용하고 embed 타입 ( play.golang.org/p/psejeXYbz5T ) 을 사용하여 이것을 보관하는 두 가지 방법이 있습니다 . 나에게 그것은 타입 변환 만 필요로하기 때문에 타입 별칭이 변환을 더 쉽게하는 것처럼 보입니다. 타입 랩을 사용하는 경우 점을 사용하여 "부모"구조를 참조해야하므로 부모 유형 자체에 액세스해야합니다. 나는 클라이언트 코드에 달려 있다고 생각한다.
Victor

제발,이 주제의 동기 부여를 참조하십시오 stackoverflow.com/a/28800807/903998 , 의견을 따르십시오 그리고 당신이 내 요점을 볼 수 있기를 바랍니다
Victor

나는 당신의 의미를 따를 수 있기를 원하지만 여전히 문제가 있습니다. 우리가 이러한 의견을 작성하는 답변에서 나는 각각의 장단점을 포함하여 임베딩 및 앨리어싱을 설명합니다. 나는 서로를 옹호하지 않습니다. 당신이 내가 그 장단점 중 하나를 놓쳤다 고 제안하고있을 수도 있습니다.
TheHerk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.