调用Go函数,该函数接受具有结构B的切片的接口a的切片(B实现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。虽然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的一部分。这一点要清楚得多,调用者可以将其内容转换为[]语句,也可以直接构造[]语句。

以下代码有两种不同的结构类型,它们都实现了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())
}

CCD_ 21对CCD_ 22没有任何改变或了解。您必须为切片CCD_ 23&Statements

最新更新