Go 语言编程 — 高级数据类型 — 接口

目录

接口

在计算机科学中,接口的本质就是引入一个新的中间层,服务端可以通过接口与具体实现分离,实现上下游的耦合,上层的调用模块不再需要关心下层的具体实现,只需要关注一个约定好的接口。

POSIX 接口(Portable Operating System Interface,可移植操作系统接口)就是一个典型的例子,它定义了应用程序接口和命令行等标准,为计算机软件带来了可移植性,只要操作系统实现了 POSIX 接口标准,计算机软件就可以直接在不同操作系统之间移植和运行。

人能够同时处理的信息非常有限,定义良好的接口能够隔离底层的实现,让我们将重点放在当前的代码片段中。

Golang 的接口

计算机科学中的接口是一个比较抽象的概念,但是编程语言中接口的概念就更加具体。Golang 中的接口是一种内置的数据类型,它定义了一组方法的签名,使用 type 和 interface 关键字来声明。使用接口能够让我们更好地组织并写出易于测试的代码,在 Golang 的实际编程中,几乎所有的数据结构都围绕接口展开。

Go 不是一种典型的 OOP 编程语言,它在语法上不支持类和继承的概念。没有继承是否就无法拥有多态特性呢?Golang 通过 interface 实现了多态。

从语法上看,Interface 定义了若干个 Method(s),这些 Method(s) 只有函数签名(函数名、形参列表、返回值),没有具体的函数体。如果某个数据类型实现了 Interface 中定义的所有 Method(s),则称这些数据类型实现了接口。这是典型的面向对象思想。

注意,Golang 也允许不带任何方法的 interface ,称为 empty interface。

格式:

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

示例:

package main

/* 定义一个 interface,内含若干个 method。 */
type MyInterface interface {
    Print()
}

/* 定义一个结构体类型。 */
type MyStruct struct {}

/* 结构体类型实现了 method,也就实现了该 method 所属的 interface。 */
func (me MyStruct) Print() {}

func TestFunc(x MyInterface) {}

func main() {
    /* 声明结构体变量。 */
    var me MyStruct
    /* 该结构体变量具有成员 method,其类型为 interface。 */
    TestFunc(me)
}

示例:

type I interface {    
    Get() int
    Set(int)
}

type S struct {
    Age int
}

// S 实现了 I 的两个方法(Set、Get),就说 S 是 I 的实现者。

func(s S) Get() int {
    return s.Age
}

func(s *S) Set(age int) {
    s.Age = age
}

func f(i I){
    i.Set(10)
    fmt.Println(i.Get())
}

func main() {
    s := S{} 
    // 结构体变量实现了接口,就可以传递到接受该接口的函数。
    // 执行 f(&s) 就完了一次 interface 类型的使用。
    f(&s)
}

可见,Golang 中的 interface,比起基本数据类型所具备的实际的操作意义而言,interface 的概念更偏向于一个编程思想的体现,是一种编程的实现机制。如上例:如果有多种类型实现了某个 interface,这些类型的变量对象都可以直接使用 f 的实现,这就是泛式编程的思想,也称为 duck typing。

在使用 interface 时不需要显式在 struct 上声明要实现哪个 interface ,只需要实现对应 interface 中的方法即可,Golang 会自动进行 interface 的检查,并在运行时执行从其他类型到 interface 的自动转换,即使实现了多个 interface,Golang 也会在使用对应 interface 时实现自动转换,这就是 interface 的魔力所在。

Golang 为什么需要接口?

在 Gopher China 上的分享中,有大神给出了下面的理由:

  • hiding implementation detail(隐藏具体实现)
  • writing generic algorithm(泛型编程)
  • providing interception points

隐藏具体实现

隐藏具体实现,这个很好理解。比如我设计一个函数给你返回一个 interface,那么你只能通过 interface 里面的方法来做一些操作,但是内部的具体实现是完全不需要关心的。

泛型编程

严格来说,Golang 并不支持泛型编程。在 C++ 等高级语言中使用泛型编程非常的简单,所以这一直是 Golang 诟病最多的地方。但是使用 interface 我们可以实现泛型编程。

package sort

// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package.  The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

...

// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
    // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
    n := data.Len()
    maxDepth := 0
    for i := n; i > 0; i >>= 1 {
        maxDepth++
    }
    maxDepth *= 2
    quickSort(data, 0, n, maxDepth)
}

上述例子,Sort() 函数的形参是一个 interface,包含了三个方法:Len()、Less() 和 Swap()。如此的,当我们调用 Sort() 时,不管数组的元素是什么数据类型(e.g. int、float、string、…),只要数据类型实现了这三个方法就可以使用 Sort 函数,这样就实现了 “泛型编程”。

示例:

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s: %d", p.Name, p.Age)
}

// ByAge implements sort.Interface for []Person based on the Age field.
type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

func main() {
    /* 定义了一个 Person 结构体数组类型变量。 */
    people := []Person {
        {"Bob", 31},
        {"John", 42},
        {"Michael", 17},
        {"Jenny", 26},
    }

    fmt.Println(people)
    sort.Sort(ByAge(people))
    fmt.Println(people)
}

示例:定义了一个接口 Phone,在这个接口里面有一个方法 call()。然后我们在 main 函数里面定义了一个 Phone(接口)类型变量,并分别为之赋值为 NokiaPhone 和 IPhone,然后调用 call() 方法。

package main

import "fmt"

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()
}

结果:

I am Nokia, I can call you!
I am iPhone, I can call you!

参考资料

https://sanyuesha.com/2017/07/22/how-to-understand-go-interface/

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/107164355