簡體   English   中英

調用Go函數接受帶有一層struct B的接口A的切片(B實現A)

[英]Call Go function that accepts a slice of interface A with a slice of struct B (B implements A)

我有以下類型:

type Statement interface {
    Say() string
}

type Quote struct {
    quote string
}

func (p Quote) Say() string {
    return p.quote
}

func Replay(conversation []Statement) {
    for _, statement := range conversation {
        fmt.Println(statement.Say())
    }
}

我想我已經很好地掌握了為什么接受類型為[]Statement的參數的函數,不能用[]Quote調用; 即使Quote實現Statement[]Quote也沒有實現[]Statement []Statement甚至不是一個接口。 它具有slice of Statement的類型slice of Statement 當Go隱式地從類型轉換為接口類型時,它不會從類型A的切片到接口B的切片進行隱式轉換。

我們可以明確地將引號轉換為語句:

conversation := []Quote{
    Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
    Quote{"Mr. Pink: Uh-uh, I don't tip."},
    Quote{"Nice Guy Eddie: You don't tip?"},
    Quote{"Mr. Pink: Nah, I don't believe in it."},
    Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}

// This doesn't work
// Replay(conversation)

// Create statements from quotes
statements := make([]Statement, len(conversation))
for i, quote := range conversation {
    statements[i] = quote
}

Replay(statements)

現在說Replay是一個圖書館的一部分,它希望能夠輕松使用Replay。 它允許您使用任何對象片段調用Replay,只要這些對象實現Statement接口即可。 為此,它具有以下轉換方法:

func ConvertToStatements(its interface{}) ([]Statement, error) {
    itsValue := reflect.ValueOf(its)
    itsKind := itsValue.Kind()
    if itsKind != reflect.Array && itsKind != reflect.Slice {
        return nil, fmt.Errorf("Expected items to be an Array or a Slice, got %s", itsKind)
    }
    itsLength := itsValue.Len()
    items := make([]Statement, itsLength)
    for i := 0; i < itsLength; i++ {
        itsItem := itsValue.Index(i)
        if item, ok := itsItem.Interface().(Statement); ok {
            items[i] = item
        } else {
            return nil, fmt.Errorf("item #%d does not implement the Statement interface: %s", i, itsItem)
        }
    }
    return items, nil
}

重播看起來像這樣:

func Replay(its interface{}) {
    conversation := ConvertToStatements(its)
    for _, statement := range conversation {
        fmt.Println(statement.Say())
    }
}

我們現在可以直接使用引號調用Replay:

Replay(conversation)

最后,我的問題是:是否有更簡單的方法允許Replay接受任何類型A的切片,只要A實現Statement接口?

[]Quote切片的內存中布局與[]Statement切片不同,因此這是不可能的。

[]Quote切片的后備數組將包含連續的Quote結構,而[]Statement切片的后備數組由接口變量組成。 除了保持Quote結構(或任何其他類型實現接口)之外,接口變量還存儲指向所包含值的類型信息的指針。 這是確定如何分派Say方法調用所必需的。

不同的數據布局意味着您無法交換兩種切片類型,甚至不能通過不安全的轉換:如果您有一種類型而需要另一種類型,則需要在它們之間手動轉換。

你(長期)問題的答案非常簡短:

我不認為你的ConvertToStatment和Replay解決方案采用空接口是一個“不錯”的解決方案:我更喜歡func Replay([]Statement)並且調用者必須提供一片Statments。 這更加清晰,調用者可以將他們的東西轉換為[] Statement或直接構造[]語句。

以下代碼有兩種不同的結構類型,它們都實現了Say()函數。 您可以創建一個包含兩種類型的數組,並調用Replay()並讓它執行您想要的操作:

package main

import "fmt"

type Statement interface {
    Say() string
}
type Statements []Statement

type Quote struct {
    quote string
}
type Quotes []Quote

func (p Quote) Say() string {
    return p.quote
}

type Attributed struct {
    who   string
    quote string
}

func (p Attributed) Say() string {
    return p.who + ": " + p.quote
}


func Replay(conversation []Statement) {
    for _, s := range conversation {
        fmt.Println(s.Say())
    }
}

func (q Quotes) toStatements() Statements {
    conv := make(Statements, len(q))
    for i, v := range q {
        conv[i] = Statement(v)
    }
    return conv
}

func main() {
    conversation := Statements{
        Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
        Quote{"Mr. Pink: Uh-uh, I don't tip."},
        Attributed{"Nice Guy Eddie", "You don't tip?"},  // <= another type
        Quote{"Mr. Pink: Nah, I don't believe in it."},
        Quote{"Nice Guy Eddie: You don't believe in tipping?"},
    }

    myquotes := Quotes{
        Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
        Quote{"Mr. Pink: Uh-uh, I don't tip."},
        Quote{"Nice Guy Eddie: You don't tip?"},
        Quote{"Mr. Pink: Nah, I don't believe in it."},
        Quote{"Nice Guy Eddie: You don't believe in tipping?"},
    }

    Replay(conversation)
    Replay(myquotes.toStatements())
}

Replay()不會更改或了解有關Attributed{}任何信息。 您必須為切片QuotesStatements引入類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM