【问题标题】:Call Go function that accepts a slice of interface A with a slice of struct B (B implements A)调用 Go 函数,该函数接受接口 A 的切片和结构 B 的切片(B 实现 A)
【发布时间】:2013-12-08 10:37:54
【问题描述】:

我有以下几种:

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 接口?

【问题讨论】:

    标签: types interface go


    【解决方案1】:

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

    []Quote 切片的后备数组将由连续的Quote 结构组成,而[]Statement 切片的后备数组由接口变量组成。除了保存Quote 结构(或任何其他实现接口的类型)外,接口变量还存储指向所包含值的类型信息的指针。这是确定如何调度Say 方法调用所必需的。

    不同的数据布局意味着您不能互换两种切片类型,即使通过不安全的强制转换也不行:如果您有一种类型并且需要另一种类型,则需要在它们之间手动转换。

    【讨论】:

      【解决方案2】:

      对您的(长)问题的简短回答是:不。

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

      【讨论】:

        【解决方案3】:

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

        【讨论】:

          猜你喜欢
          • 2012-10-11
          • 2019-07-14
          • 1970-01-01
          • 2020-03-03
          • 1970-01-01
          • 2022-11-16
          • 1970-01-01
          • 2014-11-02
          相关资源
          最近更新 更多