美文网首页
Golang interface 解析

Golang interface 解析

作者: CasperLiu | 来源:发表于2019-02-06 18:14 被阅读0次

    前言

    本文将解释Golang中interface的定义,用法,注意事项,希望对大家的工作学习提供借鉴与帮助。


    定义

    interface定义

    参考Golang Spec文档(https://golang.org/ref/spec),interface定义如下:

    An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

    意思是说:接口类型指定了一个方法集(method set),这个方法集称为该接口类型的接口。接口类型T的变量可以保存任意类型X的值,只要该类型X的方法集满足是该接口类型T的超集。这样的类型X可以说实现了接口类型T。未初始化的接口类型变量的值为nil。

    Go语言里面,声明一个接口类型需要使用type关键字、接口类型名称、interface关键字和一组有{}括起来的方法声明(method specification),这些方法声明只有方法名、参数和返回值,不需要方法体。
    如下我们声明了Bird接口类型:

    type Bird interface {
        Twitter(name string) string
        Fly(height int) bool
    }
    

    在一个接口类型中,每个方法(method)必须名字非空且唯一(unique & non-blank)

    Go语言没有继承的概念,那如果需要实现继承的效果怎么办?Go的方法是嵌入。
    接口类型支持嵌入(embedding)来实现继承的效果。
    一个接口类型T可以使用接口类型E的名字,放在方法声明的位置。称为将接口类型E嵌入到接口类型T中,这样会将接口类型E的全部方法(公开的,私有的)添加到接口类型T。
    例如:

    type ReadWriter interface {
        Read(b Buffer) bool
        Write(b Buffer) bool
    }
    type Locker interface {
        Lock()
        Unlock()
    }
    type File interface {
        ReadWriter  // same as adding the methods of ReadWriter
        Locker      // same as adding the methods of Locker
        Close()
    }
    type LockedFile interface {
        Locker
        File        // illegal: Lock, Unlock not unique
        Lock()      // illegal: Lock not unique
    }
    

    在java中,通过类来实现接口。一个类需要在声明通过implements显示说明实现哪些接口,并在类的方法中实现所有的接口方法。Go语言没有类,也没有implements,如何来实现一个接口呢?这里就体现了Go与别不同的地方了。
    首先,Go语言没有类但是有struct,通过struct来定义模型结构和方法。
    其次,Go语言实现一个接口并不需要显示声明,而是只要你实现了接口中的所有方法就认为你实现了这个接口。这称之为Duck typing。

    method set定义

    Golang Spec中对于Method Set的定义如下:
    https://golang.org/ref/spec#Method_sets

    A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

    The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.
    意思是说:
    一个类型可能有相关的方法集。接口类型的方法集就是其接口。
    其他非接口类型T的方法集是所有receiver type为类型T的方法。
    类型T相应的指针类型*T的方法集是所有receiver type为*TT的方法。
    其他的类型方法集为空。
    在一个方法集中,每个方法名字唯一且不为空。

    一个类型的方法集决定了该类型可以使实现的接口,以及使用该类型作为receiver type可以调用的方法。

    Stackoverflow针对上述晦涩的描述有非常精辟的总结:
    https://stackoverflow.com/questions/33587227/golang-method-sets-pointer-vs-value-receiver

    1. If you have a *T you can call methods that have a receiver type of *T as well as methods that have a receiver type of T (the passage you quoted, Method Sets).
    2. If you have a T and it is addressable you can call methods that have a receiver type of *T as well as methods that have a receiver type of T, because the method call t.Meth() will be equivalent to (&t).Meth() (Calls).
    3. If you have a T and it isn't addressable, you can only call methods that have a receiver type of T, not *T.
    4. If you have an interface I, and some or all of the methods in I's method set are provided by methods with a receiver of *T (with the remainder being provided by methods with a receiver of T), then *T satisfies the interface I, but T doesn't. That is because *T's method set includes T's, but not the other way around (back to the first point again).

    In short, you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables containing values and pointers, without worrying about which is which. Both will work, and the syntax is the same. However, if methods with pointer receivers are needed to satisfy an interface, then only a pointer will be assignable to the interface — a value won't be valid.


    interface示例

    package main
    
    import "fmt"
    
    // 声明Bird接口类型
    type Bird interface {
        Twitter() string
        Fly(height int) bool
    }
    
    // 声明Chicken接口类型,该接口内嵌Bird接口类型
    type Chicken interface {
        Bird
        Walk()
    }
    
    // 声明Swallow结构体
    type Swallow struct {
        name string
    }
    
    // receiver type为*Sparrow(pointer type)的method set包括方法:Twitter(), Fly()
    func (this *Swallow) Twitter() string {
        fmt.Println(this.name + " Twitter")
        return this.name
    }
    func (this *Swallow) Fly(height int) bool {
        fmt.Println(this.name + " Fly")
        return true
    }
    
    // receiver type为Swallow(value type)的method set包括方法:Walk()
    func (this Swallow) Walk() {
        fmt.Println(this.name + " Walk")
        return
    }
    
    func BirdAnimation(bird Bird, height int) {
        fmt.Printf("BirdAnimation: %T\n", bird)
        bird.Fly(height)
        bird.Twitter()
    }
    
    func ChickenAnimation(chicken Chicken) {
        fmt.Printf("ChickenAnimation: %T\n", chicken)
        chicken.Walk()
        chicken.Twitter()
    }
    func main() {
        swallow := &Swallow{name: "swallow"}
    
        // 由于*Swallow实现了Bird接口类型的所有方法,所以我们可以将*Swallow类型的变量swallow赋值给Bird interface type变量bird
        bird := swallow
        BirdAnimation(bird, 200)
        BirdAnimation(swallow, 100)
    
        var chicken Chicken
        swallow2 := Swallow{}
        chicken = &swallow2
        // variable swallow2's type is Swallow, Swallow's method set is Walk(),
        // chicken's type is Chicken, Chicken's method set is Twitter(), Fly(), Walk()
    
        // 一个指针类型(pointer type)的方法列表必然包含所有接收者为指针接收者(pointer receiver method)的方法,
        // 一个非指针类型(value type)的方法列表也包含所有接收者为非指针类型(value receiver method)的方法
        // Compile error for chicken = sparrow2,
        // cannot use sparrow2 (type Sparrow) as type Chicken in assignment:
        // Sparrow does not implement Chicken (Fly method has pointer receiver)
    
        ChickenAnimation(chicken)
    }
    
    

    注意事项

    1. interface{} slice与interface{}的转换

    首先我们看看下面例程的代码:

    func printAll(values []interface{}) { 
            for _, val := range values {    
                    fmt.Println(val)
            }
    }
    func main(){
            names := []string{"stanley", "david", "oscar"}
            printAll(names)
    }
    

    执行之后会报错:
    cannot use names (type []string) as type []interface {} in argument to printAll

    下面的文章很好的解释了为什么会有编译错误:
    https://link.jianshu.com/?t=https://github.com/golang/go/wiki/InterfaceSlice

    There are two main reasons for this.

    The first is that a variable with type []interface{} is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear.

    Well, is it? A variable with type []interface{} has a specific memory layout, known at compile time.

    Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N*2 words long.

    This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be N*sizeof(MyType) words long.

    The result is that you cannot quickly assign something of type []MyType to something of type []interface{}; the data behind them just look different.

    正确的办法是:

    It depends on what you wanted to do in the first place.

    If you want a container for an arbitrary array type, and you plan on changing back to the original type before doing any indexing operations, you can just use an interface{}. The code will be generic (if not compile-time type-safe) and fast.

    If you really want a []interface{} because you'll be doing indexing before converting back, or you are using a particular interface type and you want to use its methods, you will have to make a copy of the slice.

    var dataSlice []int = foo()
    var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
    for i, d := range dataSlice {
        interfaceSlice[i] = d
    }
    

    相关文章

      网友评论

          本文标题:Golang interface 解析

          本文链接:https://www.haomeiwen.com/subject/kscisqtx.html