我有以下类型:
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接口?
答案 0 :(得分:5)
[]Quote
切片的内存中布局与[]Statement
切片不同,因此无法做到这一点。
[]Quote
切片的后备数组将由顺序Quote
结构组成,而[]Statement
切片的后备数组由接口变量组成。除了保持Quote
结构(或任何其他类型实现接口)之外,接口变量还存储指向所包含值的类型信息的指针。这是确定如何分派Say
方法调用所必需的。
不同的数据布局意味着您无法交换两种切片类型,甚至不能通过不安全的转换:如果您有一种类型而需要另一种类型,则需要在它们之间手动转换。
答案 1 :(得分:2)
你的(长)问题的答案非常简短:否。
我不认为你的ConvertToStatment和Replay解决方案采用空接口是一个“不错”的解决方案:我更喜欢func Replay([]Statement)
,并且调用者必须提供一片Statments。这更加清晰,调用者可以将他们的东西转换为[] Statement或直接构造[]语句。
答案 2 :(得分:1)
以下代码有两种不同的结构类型,它们都实现了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{}
进行任何更改或了解。
您必须为切片Quotes
&amp; Statements
。