关于Golang 接口的理解

接口类型是一个抽象类型,它不会暴露所代表的对象的内部值的结构和这个对象所自持的基础操作的集合, 当看到一个接口类型的值时,唯一可以知道的是可以通过他来做什么。

接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例

接口是合约

一个类型如果拥有了一个接口需要的所有方法(类型拥有的方法>=接口所需要的方法),那么这个类型就实现了这个接口。

表达一个类型属于某个接口只要这个类型实现这个接口

接口类型封装和隐藏了具体类型和它的值,即使具体类型有其他的方法也只有接口类型暴露出来的方法会被调用到

所有类型都实现了空接口interface{}

实例参考源码sort/Interface接口、http/Handler接口和error接口

用接口实现“伪泛型”

可以实现例如“伪泛型” =>泛型(泛型就是C++的模板)

例子: 我们看一个冒泡排序的问题。针对整型数组切片的排序。

package main

import (
    "fmt"
)

func bubbleSort(array []int) {
    for i := 0; i < len(array); i++ {
        for j := 0; j < len(array)-i-1; j++ {
            if array[j] > array[j+1] {
                array[j], array[j+1] = array[j+1], array[j]
            }
        }
    }
}

func main() {
    a1 := []int{3, 2, 6, 10, 7, 4, 6, 5}
    bubbleSort(a1)
    fmt.Println(a1)
}

我们如果希望这个bubbleSort能够同时支持float类型数据排序,或者是按照字符串的长度来排序应该怎么做呢? 在其他的例如java语言中,我们可以将bubbleSort定义为支持泛型的排序,但是Go里面就不行了。为了达到这个目的,我们可以使用interface来实现相同的功能。 针对上面的排序问题,我们可以分析一下排序的步骤:

  • 查看切片长度,以用来遍历元素(Len);
  • 比较切片中的两个元素(Less);
  • 根据比较的结果决定是否交换元素位置(Swap)。

到这里,或许你已经明白了,我们可以把上面的函数分解为一个支持任意类型的接口,任何其他类型的数据只要实现了这个接口,就可以用这个接口中的函数来排序了。

type Sortable interface{
    Len() int
    Less(int, int) bool
    Swap(int, int)
}

下面,我们就用几个例子来描述一下这个接口的用法。

package main

import (
    "fmt"
)

type Sortable interface {
    Len() int
    Less(int, int) bool
    Swap(int, int)
}
//接口和接口函数 != 结构体和结构体函数。
func bubbleSort(array Sortable) {
    for i := 0; i < array.Len(); i++ {
        for j := 0; j < array.Len()-i-1; j++ {
            if array.Less(j+1, j) {
                array.Swap(j, j+1)
            }
        }
    }
}

//实现接口的整型切片
type IntArr []int

func (array IntArr) Len() int {
    return len(array)
}

func (array IntArr) Less(i int, j int) bool {
    return array[i] < array[j]
}

func (array IntArr) Swap(i int, j int) {
    array[i], array[j] = array[j], array[i]
}

//实现接口的字符串,按照长度排序
type StringArr []string

func (array StringArr) Len() int {
    return len(array)
}

func (array StringArr) Less(i int, j int) bool {
    return len(array[i]) < len(array[j])
}

func (array StringArr) Swap(i int, j int) {
    array[i], array[j] = array[j], array[i]
}

//测试
func main() {
    intArray1 := IntArr{3, 4, 2, 6, 10, 1}
    bubbleSort(intArray1)
    fmt.Println(intArray1)

    stringArray1 := StringArr{"hello", "i", "am", "go", "lang"}
    bubbleSort(stringArray1)
    fmt.Println(stringArray1)
}

输出结果为:

[1 2 3 4 6 10]
[i am go lang hello]

上面的例子中,我们首先定义了一个IntArr类型的整型切片类型,然后让这个类型实现了Sortable接口,然后在测试代码中,这个IntArr类型就可以直接调用Sortable接口的bubbleSort方法了。

另外,我们还演示了一个字符串切片类型StringArr按照字符串长度来排序的例子。和IntArr类型一样,它实现了Sortable即可定义的方法,然后就可以用Sortable即可的bubbleSort方法来排序了。

上面的例子,是一种Golang中支持所谓的“泛型”的方法。这种泛型当然不是真正意义上面的泛型,但是提供了一种针对多种类型的一致性方法的参考实现。

关于heap源码

参照container/heap源码的例子

type Interface interface {
    sort.Interface
    Push(x interface{}) // add x as element Len()
    Pop() interface{}   // remove and return element Len() - 1.
}

根据heap的定义,heap继承了sort.Interface这个接口 而sort.Interface,需要实现三个方法:Len(),Less(), Swap()。

同时还需要实现堆接口定义的两个方法:Push(x interface{}) / Pop() interface{},,所以类型想要实现这个接口需要实现5个方法

包含int的最小堆

// This example demonstrates an integer heap built using the heap interface.
package heap_test

import (
    "container/heap"
    "fmt"
)

// An IntHeap is a min-heap of ints.
type IntHeap []int

func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *IntHeap) Push(x interface{}) {
    // Push and Pop use pointer receivers because they modify the slice's length,
    // not just its contents.
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
func Example_intHeap() {
    h := &IntHeap{2, 1, 5}
    heap.Init(h)
    heap.Push(h, 3)
    fmt.Printf("minimum: %d\n", (*h)[0])
    for h.Len() > 0 {
        fmt.Printf("%d ", heap.Pop(h))
    }
    // Output:
    // minimum: 1
    // 1 2 3 5
}

IntHeap实现了heap接口的5个方法,所以IntHeap实现heap接口,container/heap源码中的Pop,Move等函数只是普通函数,只是参数要是heap接口,与结构体和结构体函数不一样

我的突破点

“泛型” 的代码点破我对golang接口的理解,还有上面两个例子,接口的功能还有很多,这里只是通过接口某一个功能加深对接口的理解

参考链接