Czy Go może mieć opcjonalne parametry? Czy mogę po prostu zdefiniować dwie funkcje o tej samej nazwie i innej liczbie argumentów?
Czy Go może mieć opcjonalne parametry? Czy mogę po prostu zdefiniować dwie funkcje o tej samej nazwie i innej liczbie argumentów?
Odpowiedzi:
Go nie ma opcjonalnych parametrów ani nie obsługuje przeciążania metod :
Wysyłanie metod jest uproszczone, jeśli nie trzeba również dopasowywać typów. Doświadczenie z innymi językami mówiło nam, że różnorodne metody o tej samej nazwie, ale różne podpisy okazjonalnie były przydatne, ale w praktyce może być także mylące i kruche. Dopasowywanie tylko według nazwy i wymaganie spójności typów było główną decyzją upraszczającą w systemie typów Go.
make
zatem jest przypadek szczególny? A może nie jest tak naprawdę zaimplementowany jako funkcja…
make
jest konstrukcją językową i powyższe zasady nie mają zastosowania. Zobacz to powiązane pytanie .
range
to ten sam przypadek, co make
w tym sensie
Dobrym sposobem na osiągnięcie czegoś takiego jak parametry opcjonalne jest użycie argumentów variadic. Funkcja faktycznie odbiera plasterek dowolnego określonego typu.
func foo(params ...int) {
fmt.Println(len(params))
}
func main() {
foo()
foo(1)
foo(1,2,3)
}
params
jest kawałek
Możesz użyć struktury, która zawiera parametry:
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})
W przypadku arbitralnej, potencjalnie dużej liczby parametrów opcjonalnych, dobrym idiomem jest użycie opcji funkcjonalnych .
Dla swojego typu Foobar
napisz najpierw tylko jednego konstruktora:
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
}
gdzie każda opcja jest funkcją, która mutuje Foobar. Następnie zapewnij użytkownikowi wygodne sposoby korzystania lub tworzenia standardowych opcji, na przykład:
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
}
}
Dla zwięzłości możesz nadać nazwę typowi opcji ( Plac zabaw ):
type OptionFoobar func(*Foobar) error
Jeśli potrzebujesz parametrów obowiązkowych, dodaj je jako pierwsze argumenty konstruktora przed variadic options
.
Główne zalety idiomu opcji funkcjonalnych to:
Ta technika została wymyślona przez Roba Pike'a, a także zademonstrowana przez Dave'a Cheneya .
func()
razie potrzeby s, niż pochylać mój mózg wokół tego podejścia. Ilekroć muszę korzystać z tego podejścia, na przykład w bibliotece Echo, mój mózg zostaje uwięziony w króliczej naturze abstrakcji. #fwiw
W Go nie są obsługiwane parametry opcjonalne ani przeciążanie funkcji. Go obsługuje zmienną liczbę parametrów: Przekazywanie argumentów do ... parametrów
Nie - nie. Dokumenty dla programistów Per the Go for C ++ ,
Go nie obsługuje przeciążania funkcji i nie obsługuje operatorów zdefiniowanych przez użytkownika.
Nie mogę znaleźć równie jasnego stwierdzenia, że parametry opcjonalne nie są obsługiwane, ale też nie są obsługiwane.
Możesz to całkiem dobrze zamknąć w func podobnym do tego, co poniżej.
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
}
W tym przykładzie monit domyślnie ma dwukropek i spację przed nim. . .
:
. . . można jednak to zmienić, podając parametr do funkcji pytania.
prompt("Input here -> ")
Spowoduje to wyświetlenie monitu jak poniżej.
Input here ->
Skończyło się na tym, że użyłem kombinacji struktury params i różnych argumentów. W ten sposób nie musiałem zmieniać istniejącego interfejsu, który był używany przez kilka usług, a moja usługa mogła przekazać dodatkowe parametry w razie potrzeby. Przykładowy kod na placu zabaw Golang: https://play.golang.org/p/G668FA97Nu
Jestem trochę spóźniony, ale jeśli lubisz płynny interfejs, możesz zaprojektować setery do połączeń łańcuchowych w następujący sposób:
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
}
A potem nazwij to tak:
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)
}
}
Jest to podobne do idiomu opcji funkcjonalnych przedstawionego na odpowiedzi @Ripounet i ma te same zalety, ale ma pewne wady:
err
zmienną i ją zerujący.Istnieje jednak pewna niewielka zaleta, tego typu wywołania funkcji powinny być łatwiejsze dla kompilatora do wstawienia, ale tak naprawdę nie jestem specjalistą.
Możesz przekazać dowolne nazwane parametry za pomocą mapy.
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})
}
Inną możliwością byłoby użycie struktury, która z polem wskazuje, czy jest poprawna. Wygodne są typy zerowe z SQL, takie jak NullString . Miło jest nie musieć definiować własnego typu, ale jeśli potrzebujesz niestandardowego typu danych, zawsze możesz zastosować ten sam wzorzec. Myślę, że opcjonalność wynika z definicji funkcji i wymaga minimalnego dodatkowego kodu lub wysiłku.
Jako przykład:
func Foo(bar string, baz sql.NullString){
if !baz.Valid {
baz.String = "defaultValue"
}
// the rest of the implementation
}