在计算机科学中,接口的本质就是引入一个新的中间层,服务端可以通过接口与具体实现分离,实现上下游的耦合,上层的调用模块不再需要关心下层的具体实现,只需要关注一个约定好的接口。
POSIX 接口(Portable Operating System Interface,可移植操作系统接口)就是一个典型的例子,它定义了应用程序接口和命令行等标准,为计算机软件带来了可移植性,只要操作系统实现了 POSIX 接口标准,计算机软件就可以直接在不同操作系统之间移植和运行。
人能够同时处理的信息非常有限,定义良好的接口能够隔离底层的实现,让我们将重点放在当前的代码片段中。
计算机科学中的接口是一个比较抽象的概念,但是编程语言中接口的概念就更加具体。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 的魔力所在。
在 Gopher China 上的分享中,有大神给出了下面的理由:
隐藏具体实现,这个很好理解。比如我设计一个函数给你返回一个 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/