关于类型:Call 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)

我有以下类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的切片。

我们可以将引号显式转换为语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

现在假设重播是一个库的一部分,它希望在使用重播时有多容易走出自己的路。它允许您使用任何对象片调用重播,只要这些对象实现语句接口。为此,它具有以下转换方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
}

重播如下:

1
2
3
4
5
6
func Replay(its interface{}) {
    conversation := ConvertToStatements(its)
    for _, statement := range conversation {
        fmt.Println(statement.Say())
    }
}

我们现在可以直接调用带引号的重播:

1
Replay(conversation)

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


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

[]Quote片的支持数组由顺序的Quote结构组成,而[]Statement片的支持数组由接口变量组成。除了保留Quote结构(或其他实现接口的类型),接口变量还存储一个指向所包含值的类型信息的指针。这需要确定如何调度Say方法调用。

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


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

我不认为你的转换状态和重放处理空接口的解决方案是一个"好"的解决方案:我更喜欢func Replay([]Statement),调用方必须提供一部分语句。这就更清楚了,调用者可以将其内容转换为[]语句,也可以直接构造一个[]语句。


下面的代码有两种不同的结构类型,它们都实现了Say()函数。您可以创建一个包含这两种类型的数组,并调用Replay()并让它按您的需要执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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引入类型。