调用Go函数接受带有一层struct B的接口A的切片(B实现A)

Rob*_*jic 5 types interface go

我有以下类型:

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

我想我已经很好地掌握了为什么接受类型参数的函数[]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)
Run Code Online (Sandbox Code Playgroud)

现在说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
}
Run Code Online (Sandbox Code Playgroud)

重播看起来像这样:

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

我们现在可以直接使用引号调用Replay:

Replay(conversation)
Run Code Online (Sandbox Code Playgroud)

最后,我的问题是:是否有更简单的方法允许Replay接受任何类型A的切片,只要A实现Statement接口?

Jam*_*dge 5

[]Quote切片的内存中布局与切片不同[]Statement,因此这是不可能的.

所述的背衬阵列[]Quote片将包括连续的Quote结构,而[]Statement片的背衬阵列由接口变量.除了保存Quote结构(或任何其他类型实现接口)之外,接口变量还存储指向所包含值的类型信息的指针.这是确定如何调度Say方法调用所必需的.

不同的数据布局意味着您无法交换两种切片类型,甚至不能通过不安全的转换:如果您有一种类型而需要另一种类型,则需要在它们之间手动转换.


Vol*_*ker 3

对你的(长)问题的非常简短的回答是:不。

我不认为您的 ConvertToStatment 和 Replay 采用空接口的解决方案是一个“好的”解决方案:我更喜欢func Replay([]Statement)调用者必须提供一部分 Statments。这更加清晰,调用者可以将他们的内容转换为 []Statement 或直接构造一个 []Statement。