Làm cách nào để thêm phương thức mới vào loại hiện có trong Go?


129

Tôi muốn thêm một phương thức sử dụng tiện lợi vào các loại gorilla/muxTuyến và Bộ định tuyến:

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

nhưng trình biên dịch thông báo cho tôi

Không thể xác định phương thức mới trên loại mux không phải cục bộ.

Vì vậy, làm thế nào tôi sẽ đạt được điều này? Tôi có tạo một kiểu cấu trúc mới có các trường mux.Route và mux.Router ẩn danh không? Hay cái gì khác?


Các phương thức mở rộng thú vị được coi là không hướng đối tượng ( “extension methods are not object-oriented”) cho C #, nhưng khi nhìn vào chúng ngày hôm nay, tôi đã nhớ ngay đến các giao diện của Go (và cách tiếp cận của nó để suy nghĩ lại về hướng đối tượng), và sau đó tôi đã có câu hỏi này.
Sói

Câu trả lời:


173

Khi trình biên dịch đề cập, bạn không thể mở rộng các loại hiện có trong gói khác. Bạn có thể xác định bí danh hoặc gói phụ của riêng mình như sau:

type MyRouter mux.Router

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

hoặc bằng cách nhúng bộ định tuyến gốc:

type MyRouter struct {
    *mux.Router
}

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

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

10
Hay chỉ sử dụng một chức năng ...?
Paul Hankin

5
@Paul làm điều này là bắt buộc để ghi đè các hàm như String () và MarshalJSON ()
Đi xe đạp

31
Nếu bạn làm phần đầu tiên, thì làm thế nào để bạn ép buộc các mux.Routertrường hợp thành MyRouters? ví dụ: nếu bạn có một thư viện trả về mux.Routernhưng bạn muốn sử dụng các phương thức mới của mình?
docwhat

Làm thế nào để sử dụng giải pháp đầu tiên? MyRouter (bộ định tuyến)
tfzxyinhao

nhúng có vẻ thực tế hơn một chút để sử dụng.
ivanjovanovic

124

Tôi muốn mở rộng câu trả lời được đưa ra bởi @jimt ở đây . Câu trả lời đó là chính xác và giúp tôi rất nhiều trong việc phân loại này. Tuy nhiên, có một số cảnh báo cho cả hai phương thức (bí danh, nhúng) mà tôi gặp rắc rối.

lưu ý : Tôi sử dụng thuật ngữ cha mẹ và con cái, mặc dù tôi không chắc đó là cách tốt nhất để sáng tác. Về cơ bản, cha mẹ là loại mà bạn muốn sửa đổi cục bộ. Trẻ em là loại mới cố gắng thực hiện sửa đổi đó.

Phương pháp 1 - Định nghĩa kiểu

type child parent
// or
type MyThing imported.Thing
  • Cung cấp quyền truy cập vào các lĩnh vực.
  • Không cung cấp quyền truy cập vào các phương thức.

Phương pháp 2 - Nhúng ( tài liệu chính thức )

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Cung cấp quyền truy cập vào các lĩnh vực.
  • Cung cấp quyền truy cập vào các phương pháp.
  • Yêu cầu xem xét để khởi tạo.

Tóm lược

  • Sử dụng phương thức thành phần, cha mẹ nhúng sẽ không khởi tạo nếu nó là một con trỏ. Cha mẹ phải được khởi tạo riêng.
  • Nếu cha mẹ được nhúng là một con trỏ và không được khởi tạo khi con được khởi tạo, sẽ xảy ra lỗi vô hiệu hóa con trỏ con trỏ.
  • Cả hai định nghĩa kiểu và trường hợp nhúng cung cấp quyền truy cập vào các trường của cha mẹ.
  • Định nghĩa kiểu không cho phép truy cập vào các phương thức của cha mẹ, nhưng nhúng cha mẹ thì không.

Bạn có thể thấy điều này trong đoạn mã sau.

ví dụ làm việc trên sân chơi

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

bài đăng của bạn rất hữu ích vì cho thấy rất nhiều nghiên cứu và nỗ lực cố gắng so sánh từng điểm từng tecniche .. cho phép tôi khuyến khích bạn nghĩ điều gì xảy ra khi chuyển đổi sang giao diện cụ thể. Ý tôi là, nếu bạn có một cấu trúc và bạn muốn cấu trúc đó (từ nhà cung cấp bên thứ ba hãy giả sử) bạn muốn thích nghi với một giao diện nhất định, bạn quản lý ai để có được điều đó? Bạn có thể sử dụng bí danh loại hoặc loại nhúng cho điều đó.
Victor

@Victor Tôi không theo dõi câu hỏi của bạn, nhưng tôi nghĩ rằng bạn hỏi làm thế nào để có được một cấu trúc mà bạn không kiểm soát để đáp ứng một giao diện nhất định. Câu trả lời ngắn gọn, bạn không ngoại trừ bằng cách đóng góp cho cơ sở mã đó. Tuy nhiên, bằng cách sử dụng tài liệu trong bài đăng này, bạn có thể tạo một cấu trúc khác từ đầu tiên, sau đó thực hiện giao diện trên cấu trúc đó. Xem ví dụ sân chơi này .
TheHerk

hi @TheHerk, mục đích của tôi là gì để chỉ ra một sự khác biệt khác khi "mở rộng" một cấu trúc từ gói khác. Có vẻ như với tôi, có hai cách để lưu trữ điều này, bằng cách sử dụng bí danh loại (ví dụ của bạn) và sử dụng loại nhúng ( play.golang.org/p/psejeXYbz5T ). Đối với tôi, có vẻ như bí danh loại đó giúp việc chuyển đổi dễ dàng hơn vì bạn chỉ cần chuyển đổi loại , nếu bạn sử dụng kiểu bao bọc, bạn cần tham chiếu cấu trúc "cha mẹ" bằng cách sử dụng dấu chấm, do đó, truy cập vào chính kiểu cha mẹ. Tôi đoán là tùy thuộc vào mã khách hàng ...
Victor

xin vui lòng, xem động lực của chủ đề này ở đây stackoverflow.com/a/28800807/903998 , theo dõi các ý kiến ​​và tôi hy vọng bạn sẽ thấy quan điểm của tôi
Victor

Tôi ước tôi có thể làm theo ý của bạn, nhưng tôi vẫn gặp rắc rối. Trong câu trả lời mà chúng tôi đang viết những bình luận này, tôi giải thích cả việc nhúng và răng cưa, bao gồm cả những ưu điểm và nhược điểm của mỗi bình luận. Tôi không ủng hộ cái này hơn cái kia. Có thể là bạn đang đề nghị tôi bỏ lỡ một trong những ưu và nhược điểm đó.
TheHerk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.