Thông số tùy chọn trong Go?


464

Đi có thể có các tham số tùy chọn? Hoặc tôi chỉ có thể định nghĩa hai hàm có cùng tên và số lượng đối số khác nhau?


Liên quan: đây là cách có thể được thực hiện để thực thi các tham số bắt buộc khi sử dụng matrixdic làm tham số tùy chọn: Có thể kích hoạt lỗi thời gian biên dịch với thư viện tùy chỉnh trong golang không?
icza

11
Google đã đưa ra một quyết định tồi tệ, bởi vì đôi khi một chức năng có 90% trường hợp sử dụng và sau đó là 10% trường hợp sử dụng. Các đối số tùy chọn là cho trường hợp sử dụng 10% đó. Mặc định Sane có nghĩa là ít mã hơn, ít mã hơn có nghĩa là dễ bảo trì hơn.
Jonathan

Câu trả lời:


431

Go không có các tham số tùy chọn cũng như không hỗ trợ nạp chồng phương thức :

Công văn phương thức được đơn giản hóa nếu nó không cần phải thực hiện khớp loại. Kinh nghiệm với các ngôn ngữ khác nói với chúng tôi rằng có nhiều phương pháp có cùng tên nhưng chữ ký khác nhau đôi khi rất hữu ích nhưng nó cũng có thể gây nhầm lẫn và mong manh trong thực tế. Chỉ khớp với tên và yêu cầu tính nhất quán trong các loại là một quyết định đơn giản hóa lớn trong hệ thống loại của Go.


58
makemột trường hợp đặc biệt, sau đó? Hoặc nó thậm chí không thực sự được thực hiện như là một chức năng
mk12

65
@ Mk12 makelà cấu trúc ngôn ngữ và các quy tắc được đề cập ở trên không áp dụng. Xem câu hỏi liên quan này .
nemo

7
rangelà trường hợp tương tự make, theo nghĩa đó
thiagowfx

14
Quá tải phương pháp - Một ý tưởng tuyệt vời trong lý thuyết và xuất sắc khi được thực hiện tốt. Tuy nhiên, tôi đã chứng kiến ​​quá tải rác không thể giải mã được trong thực tế và do đó sẽ đồng ý với quyết định của Google
trevorgk

118
Tôi sẽ đi ra ngoài và không đồng ý với lựa chọn này. Các nhà thiết kế ngôn ngữ về cơ bản đã nói: "Chúng tôi cần quá tải chức năng để thiết kế ngôn ngữ mà chúng tôi muốn, vì vậy, phạm vi, v.v ... về cơ bản là quá tải, nhưng nếu bạn muốn quá tải chức năng để thiết kế API bạn muốn, thì, đó là khó khăn." Việc một số lập trình viên sử dụng sai một tính năng ngôn ngữ không phải là một đối số để loại bỏ tính năng này.
Tom

216

Một cách hay để đạt được một cái gì đó giống như các tham số tùy chọn là sử dụng args matrixdic. Hàm thực sự nhận được một lát của bất kỳ loại nào bạn chỉ định.

func foo(params ...int) {
    fmt.Println(len(params))
}

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}

"Hàm thực sự nhận được một lát của bất kỳ loại nào bạn chỉ định" làm thế nào?
Alix Axel

3
trong ví dụ trên, paramslà một lát ints
Ferguzz 17/214

76
Nhưng chỉ dành cho cùng loại thông số :(
Juan de Parras

15
@JuandeParras Vâng, bạn vẫn có thể sử dụng một cái gì đó như ... giao diện {} tôi đoán.
maufl

5
Với ... loại bạn không truyền đạt ý nghĩa của các tùy chọn riêng lẻ. Sử dụng một cấu trúc thay thế. ... loại này có ích cho các giá trị mà nếu không bạn sẽ phải đặt vào một mảng trước cuộc gọi.
dùng3523091

170

Bạn có thể sử dụng một cấu trúc bao gồm các tham số:

type Params struct {
  a, b, c int
}

func doIt(p Params) int {
  return p.a + p.b + p.c 
}

// you can call it without specifying all parameters
doIt(Params{a: 1, c: 9})

12
Sẽ thật tuyệt nếu các cấu trúc có thể có các giá trị mặc định ở đây; bất cứ điều gì người dùng bỏ qua được mặc định là giá trị không cho loại đó, có thể hoặc không thể là một đối số mặc định phù hợp cho hàm.
jsdw

41
@lytnus, tôi ghét phải chia tóc, nhưng các trường bị bỏ qua các giá trị sẽ mặc định là 'giá trị 0' cho loại của chúng; nil là một động vật khác nhau. Nếu loại trường bị bỏ qua xảy ra là một con trỏ, giá trị 0 sẽ là con số không.
burfl

2
@burfl yeah, ngoại trừ khái niệm "zero value" hoàn toàn vô dụng đối với các kiểu int / float / chuỗi, bởi vì các giá trị đó có ý nghĩa và vì vậy bạn không thể biết sự khác biệt nếu giá trị bị bỏ qua khỏi struct hoặc nếu giá trị zero là thông qua cố ý.
keymone

3
@keymone, tôi không đồng ý với bạn. Tôi chỉ đơn thuần là nói xấu về tuyên bố ở trên rằng các giá trị được người dùng bỏ qua mặc định là "giá trị không cho loại đó", không chính xác. Chúng mặc định là giá trị 0, có thể có hoặc không bằng 0, tùy thuộc vào loại đó có phải là con trỏ hay không.
burfl

124

Đối với tùy ý, có thể có số lượng lớn các tham số tùy chọn, một thành ngữ hay là sử dụng các tùy chọn Chức năng .

Đối với loại của bạn Foobar, đầu tiên chỉ viết một hàm tạo:

func NewFoobar(options ...func(*Foobar) error) (*Foobar, error){
  fb := &Foobar{}
  // ... (write initializations with default values)...
  for _, op := range options{
    err := op(fb)
    if err != nil {
      return nil, err
    }
  }
  return fb, nil
}

trong đó mỗi tùy chọn là một chức năng làm thay đổi Foobar. Sau đó, cung cấp các cách thuận tiện cho người dùng của bạn sử dụng hoặc tạo các tùy chọn tiêu chuẩn, ví dụ:

func OptionReadonlyFlag(fb *Foobar) error {
  fb.mutable = false
  return nil
}

func OptionTemperature(t Celsius) func(*Foobar) error {
  return func(fb *Foobar) error {
    fb.temperature = t
    return nil
  }
}

Sân chơi

Để đơn giản, bạn có thể đặt tên cho loại tùy chọn ( Sân chơi ):

type OptionFoobar func(*Foobar) error

Nếu bạn cần các tham số bắt buộc, hãy thêm chúng làm đối số đầu tiên của hàm tạo trước biến động options.

Những lợi ích chính của thành ngữ Tùy chọn chức năng là:

  • API của bạn có thể phát triển theo thời gian mà không vi phạm mã hiện có, vì chữ ký của người giữ nguyên vẫn giữ nguyên khi cần các tùy chọn mới.
  • nó cho phép trường hợp sử dụng mặc định trở nên đơn giản nhất: không có đối số nào cả!
  • nó cung cấp sự kiểm soát tốt đối với việc khởi tạo các giá trị phức tạp.

Kỹ thuật này được tạo ra bởi Rob Pike và cũng được thể hiện bởi Dave Cheney .



15
Khéo léo, nhưng quá phức tạp. Triết lý của Go là viết mã theo cách đơn giản. Chỉ cần vượt qua một cấu trúc và kiểm tra các giá trị mặc định.
dùng3523091

9
Chỉ cần FYI, tác giả ban đầu của thành ngữ này, ít nhất là nhà xuất bản đầu tiên được tham chiếu, là Chỉ huy Rob Pike, người mà tôi cho là đủ thẩm quyền cho triết lý Go. Liên kết - Commandcenter.blogspot.bg/2014/01/ . Cũng tìm kiếm cho "Đơn giản là phức tạp".
Petar Donchev

2
#JMTCW, nhưng tôi thấy cách tiếp cận này rất khó để lý do. Tôi rất muốn chuyển qua một cấu trúc của các giá trị, mà các thuộc tính của nó có thể là func()s nếu cần, hơn là uốn cong bộ não của tôi xung quanh phương pháp này. Bất cứ khi nào tôi phải sử dụng phương pháp này, chẳng hạn như với thư viện Echo, tôi thấy não của mình bị kẹt trong lỗ thỏ trừu tượng. #fwiw
MikeSchinkel 20/03/19

cảm ơn, đã tìm kiếm thành ngữ này. Cũng có thể được lên đó như là câu trả lời được chấp nhận.
r --------- k


6

Không - không. Per the Go for C ++ lập trình tài liệu,

Go không hỗ trợ nạp chồng hàm và không hỗ trợ các toán tử do người dùng xác định.

Tôi không thể tìm thấy một tuyên bố rõ ràng như nhau rằng các tham số tùy chọn không được hỗ trợ, nhưng chúng cũng không được hỗ trợ.


8
"Không có kế hoạch hiện tại cho [thông số tùy chọn] này." Ian Lance Taylor, nhóm ngôn ngữ Go. Groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Không có toán tử do Người dùng xác định là một quyết định tồi tệ, vì nó là cốt lõi đằng sau bất kỳ thư viện toán học trơn tru nào, chẳng hạn như các sản phẩm chấm hoặc các sản phẩm chéo cho đại số tuyến tính, thường được sử dụng trong đồ họa 3D.
Jonathan

4

Bạn có thể gói gọn điều này khá độc đáo trong một func tương tự như bên dưới.

package main

import (
        "bufio"
        "fmt"
        "os"
)

func main() {
        fmt.Println(prompt())
}

func prompt(params ...string) string {
        prompt := ": "
        if len(params) > 0 {
                prompt = params[0]
        }
        reader := bufio.NewReader(os.Stdin)
        fmt.Print(prompt)
        text, _ := reader.ReadString('\n')
        return text
}

Trong ví dụ này, lời nhắc theo mặc định có dấu hai chấm và khoảng trắng phía trước nó. . .

: 

. . . tuy nhiên bạn có thể ghi đè lên điều đó bằng cách cung cấp một tham số cho hàm prompt.

prompt("Input here -> ")

Điều này sẽ dẫn đến một dấu nhắc như dưới đây.

Input here ->

3

Tôi đã kết thúc bằng cách sử dụng kết hợp một cấu trúc của params và args. Bằng cách này, tôi đã không phải thay đổi giao diện hiện có được sử dụng bởi một số dịch vụ và dịch vụ của tôi có thể vượt qua các thông số bổ sung khi cần. Mã mẫu trong sân chơi golang: https://play.golang.org/p/G668FA97Nu


3

Ngôn ngữ Go không hỗ trợ nạp chồng phương thức, nhưng bạn có thể sử dụng các đối số matrixdic giống như các tham số tùy chọn, bạn cũng có thể sử dụng giao diện {} làm tham số nhưng nó không phải là một lựa chọn tốt.


2

Tôi hơi muộn, nhưng nếu bạn thích giao diện lưu loát, bạn có thể thiết kế setters của mình cho các cuộc gọi bị xiềng xích như thế này:

type myType struct {
  s string
  a, b int
}

func New(s string, err *error) *myType {
  if s == "" {
    *err = errors.New(
      "Mandatory argument `s` must not be empty!")
  }
  return &myType{s: s}
}

func (this *myType) setA (a int, err *error) *myType {
  if *err == nil {
    if a == 42 {
      *err = errors.New("42 is not the answer!")
    } else {
      this.a = a
    }
  }
  return this
}

func (this *myType) setB (b int, _ *error) *myType {
  this.b = b
  return this
}

Và sau đó gọi nó như thế này:

func main() {
  var err error = nil
  instance :=
    New("hello", &err).
    setA(1, &err).
    setB(2, &err)

  if err != nil {
    fmt.Println("Failed: ", err)
  } else {
    fmt.Println(instance)
  }
}

Điều này tương tự như thành ngữ Tùy chọn chức năng được trình bày trên câu trả lời @Ripounet và có cùng lợi ích nhưng có một số nhược điểm:

  1. Nếu xảy ra lỗi, nó sẽ không hủy bỏ ngay lập tức, do đó, nó sẽ kém hiệu quả hơn một chút nếu bạn mong muốn nhà xây dựng của mình báo cáo lỗi thường xuyên.
  2. Bạn sẽ phải dành một dòng khai báo một errbiến và zeroing nó.

Tuy nhiên, có một lợi thế nhỏ có thể xảy ra, kiểu gọi hàm này sẽ dễ dàng hơn cho trình biên dịch nội tuyến nhưng tôi thực sự không phải là chuyên gia.


đây là mẫu xây dựng
UmNyobe

2

Bạn có thể truyền các tham số được đặt tên tùy ý bằng bản đồ.

type varArgs map[string]interface{}

func myFunc(args varArgs) {

    arg1 := "default" // optional default value
    if val, ok := args["arg1"]; ok {
        // value override or other action
        arg1 = val.(string) // runtime panic if wrong type
    }

    arg2 := 123 // optional default value
    if val, ok := args["arg2"]; ok {
        // value override or other action
        arg2 = val.(int) // runtime panic if wrong type
    }

    fmt.Println(arg1, arg2)
}

func Test_test() {
    myFunc(varArgs{"arg1": "value", "arg2": 1234})
}

Dưới đây là một số bình luận về cách tiếp cận này: reddit.com/r/golang/comments/546g4z/NH
nobar


0

Một khả năng khác là sử dụng một cấu trúc có trường để cho biết nó có hợp lệ hay không. Các loại null từ sql như NullString là thuận tiện. Thật tuyệt khi không phải xác định loại của riêng bạn, nhưng trong trường hợp bạn cần một loại dữ liệu tùy chỉnh, bạn luôn có thể theo cùng một mẫu. Tôi nghĩ rằng tính năng tùy chọn rõ ràng từ định nghĩa hàm và có thêm mã hoặc nỗ lực tối thiểu.

Ví dụ:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
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.