Goにオプションのパラメータを設定できますか?または、同じ名前で引数の数が異なる2つの関数を定義することはできますか?
15 に答える
Goにはオプションのパラメーターがなく、メソッドのオーバーロードもサポートしていません。
タイプマッチングも行う必要がない場合は、メソッドディスパッチが簡略化されます。他の言語での経験から、同じ名前で署名が異なるさまざまなメソッドを使用すると便利な場合がありますが、実際には混乱して壊れやすくなる可能性があることがわかりました。名前だけで一致し、型の一貫性を要求することは、Goの型システムにおける主要な単純化の決定でした。
オプションのパラメーターのようなものを実現する良い方法は、可変引数を使用することです。この関数は、指定したタイプのスライスを実際に受け取ります。
func foo(params ...int) {
fmt.Println(len(params))
}
func main() {
foo()
foo(1)
foo(1,2,3)
}
パラメータを含む構造体を使用できます。
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})
任意の潜在的に多数のオプション パラメータの場合、便利なイディオムはFunctional optionsを使用することです。
typeFoobar
の場合、最初にコンストラクターを 1 つだけ記述します。
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
}
各オプションは、Foobar を変更する関数です。次に、ユーザーが標準オプションを使用または作成するための便利な方法を提供します。次に例を示します。
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
}
}
簡潔にするために、オプションのタイプに名前を付けることができます ( Playground ) :
type OptionFoobar func(*Foobar) error
必須パラメーターが必要な場合は、コンストラクターの最初の引数として variadic の前に追加しますoptions
。
Functional optionsイディオムの主な利点は次のとおりです。
- 新しいオプションが必要な場合でもコンストラクターの署名は同じままであるため、API は既存のコードを壊すことなく時間の経過とともに拡張できます。
- これにより、デフォルトのユース ケースが最も単純になり、引数がまったくなくなります。
- 複雑な値の初期化を細かく制御できます。
この手法は、Rob Pikeによって考案され、 Dave Cheneyによって実証されました。
Go では、オプションのパラメーターも関数のオーバーロードもサポートされていません。Go は可変数のパラメーターをサポートしています:引数を ... パラメーターに渡す
いいえ-どちらでもありません。Go for C ++プログラマーのドキュメントによると、
Goは関数のオーバーロードをサポートしておらず、ユーザー定義の演算子もサポートしていません。
オプションのパラメーターがサポートされていないという同様に明確なステートメントを見つけることはできませんが、それらもサポートされていません。
これは、以下に示すような func に非常にうまくカプセル化できます。
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
}
この例では、デフォルトでプロンプトの前にコロンとスペースがあります。. .
:
. . . ただし、プロンプト関数にパラメーターを指定することで、それをオーバーライドできます。
prompt("Input here -> ")
これにより、以下のようなプロンプトが表示されます。
Input here ->
最終的に、params の構造と可変引数の組み合わせを使用しました。このようにして、いくつかのサービスによって消費される既存のインターフェイスを変更する必要がなくなり、サービスは必要に応じて追加のパラメーターを渡すことができました。golang プレイグラウンドのサンプル コード: https://play.golang.org/p/G668FA97Nu
ポインターを使用したくない場合は、ポインターを使用して nil のままにすることができます。
func getPosts(limit *int) {
if optParam != nil {
// fetch posts with limit
} else {
// fetch all posts
}
}
func main() {
// get Posts, limit by 2
limit := 2
getPosts(&limit)
// get all posts
getPosts(nil)
}
私は少し遅れていますが、流暢なインターフェースが好きなら、次のような連鎖呼び出し用のセッターを設計することができます:
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
}
そして、次のように呼び出します。
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)
}
}
これは、@Ripounet の回答で提示されたFunctional optionsイディオムに似ており、同じ利点を享受しますが、いくつかの欠点があります。
- エラーが発生してもすぐには中止されないため、コンストラクターが頻繁にエラーを報告することが予想される場合は、効率がわずかに低下します。
err
変数を宣言してゼロにする行を費やす必要があります。
ただし、可能性のある小さな利点があります。このタイプの関数呼び出しは、コンパイラがインライン化するのが簡単になるはずですが、私は専門家ではありません。
もう 1 つの可能性は、フィールドが有効かどうかを示す構造体を使用することです。NullStringなどの sql の null 型は便利です。独自の型を定義する必要がないのは良いことですが、カスタム データ型が必要な場合は、常に同じパターンに従うことができます。オプション性は関数定義から明らかであり、余分なコードや労力は最小限に抑えられていると思います。
例として:
func Foo(bar string, baz sql.NullString){
if !baz.Valid {
baz.String = "defaultValue"
}
// the rest of the implementation
}