接口

[TOC]

1. 接口定义

接口是面向对象编程中的概念,其基本含义是接口定义了一个对象的基本行为。一个接口定义了一个对象应该去做什么what,而具体行为如何实现就取决于对象本身how

在golang中,接口是一个方法签名的集合。当一个类型提供了对于接口中所有方法的定义,那么就可以说该类型实现了该接口,这与OOP的概念极为相似。也就是说接口确定了一个类型该有的所有方法,一个类型确定了如何实现这些方法。

2. 声明以及实现

声明一个接口的基本语法是

type name interface {
    Method1() Type
    Method1() Type
    ...
}

对于已经声明的任何接口,可以使用定义在同一个包内的任何一个类型实现该接口的所有方法,此时该类型就是实现了该接口。

type VowelsFinder interface {
  FindVowels() []rune
}

const VOWELS = "aeiouAEIOU"

// myString implement the VowelsFinder interface
func (s myString) FindVowels() []rune {
  var vowels []rune
  for _, char := range s {
    for _, target := range VOWELS {
      if char == target {
        vowels = append(vowels, char)
      }
    }
  }

  return vowels
}

以上代码定义了一个VowelsFinder 的接口,并且使用string类型的别名实现了该接口中的方法。FindVowels()方法的接收器是myString 。对于一个接口的实现不需要借助类的概念,只需要是在包内良好定义的类型即可。但是根据方法的性质以及接口的性质,使以接口变量或者使用类型变量都可以对于接口中的方法进行调用。

2.1 一个典型的接口应用

接口一般用于实现可复用的面向对象的代码,通过使用接口可以实现继承的概念。

如上述代码,存在两种员工,为了进行统一的工资计算,可以使用接口并且定义一个计算工资的方法,然后让两种类型分别实现该方法,所以可以通过将两种对象归一化为接口对象然后调用同一个方法CalculateSalary()。而计算所有员工的工资时,就可以使用接口类型的切片作为参数,此时两种类型的员工都可以作为参数进行传递。

使用接口的最大的好处是,可以大大提升代码的可复用性,对于新添加的类型可以不需要改变TotalSalary()的实现。

2.2 接口类型的内部表示

一个接口类型的内部表示可以用一个元组来表示,(type,value) type字段依赖于底层的具体的类型,value是具体类型的值。对于实现了一个特定接口的类型而言,类型与实际中的对象类型一致。

以上代码声明了一个接口并且使用double类型进行实现,对于一个没有具体实现的接口变量而言,其类型以及值都是nil ,而一个接口变量可以指向一个已经实现了该接口的具体的一个类型,此时该接口变量的类型以及值都变成了具体的指向的变量.

2.3 空接口

一个接口没有定义任何方法就是空接口,可以使用interface{}来定义一个空接口。由于空接口没有任何方法,所以任何一个类型都可以看做实现了该接口,也就是说一个空接口变量可以指向一个任意类型的变量。相当于面向对象中的Object基类。可以用作函数的参数用以接受任意类型的参数.

可以借助空接口实现类型的确认,对于一个空接口变量而言,可以使用i.(Type)的语法获取该变量对应于类型Type 的值。如果真是类型是type的化可以获得相应的值,如果不是,会返回一个panic错误。可以根据返回值来判断是否属于该类型。

2.4 类型比较

可以借助空接口实现类型的比较,当然接口之间也可以进行类型的比较。可以使用switch...case来进行类型的判断,需要获取一个变量的类型,可以使用i.(type) 的语法。

但是注意需要获取一个空接口变量真实类型的值后才可以调用该类型的方法。所以在switch后通常需要进行赋值操作

3. 接口实现时的不同接收器

对于一个特定的类型,实现一个接口需要实现接口提供的所有方法,由于method可以使用值或者指针两种接收器。所以在使用接口变量调用方法时,因为接收器类型的不同会存在一定的差异。但是对于类型变量调用该方法时不存在接收器的差异。

在使用接口变量时:

  • 如果接收器是值value,那么可以使用value, pointer向接收器变量赋值,然后进行调用

  • 如果接收器是指针那么只可以使用pointer向接收器变量赋值,不可以使用value赋值

如上述代码,对于类型变量a1可以使用指针类型或者使用值形式调用接扩中定义的方法,使用指针时编译器会自动为指针进行解引用操作dereference

对于使用value的实现接口的类型,可以使用value或者pointer向接口变量赋值。但是使用pointer接收器的类型,只可以使用pointer向接口赋值。因为Tester接口是*Address实现的,Address 没有进行实现。原因是因为可以使用value调用的方法必须有一个指向该值的指针或者说可以取得该值的地址,但是对于存储在接口变量中的具体指不可以取得其地址,因此也就不可以直接进行调用,也就无法完成赋值。

4. 高级用法

4.1 实现多个接口

一个type可以实现多个接口,分别实现不同的功能。

output:

在以上代码中,两种type实现了两个接口,对于SalaryChanger接口中方法的实现,使用的是指针类型的接收器,对于SalaryCalculator接口的实现,使用的是value的接收器。并且定义了统一的接口SalaryOption作为两个接口的逻辑“父接口”,可以使用该变量调用所有操作。

4.2 接口嵌套

尽管golang中没有类的概念,也不提供继承机制,但是可以通过接口实现相似的功能,可以使用接口嵌套实现。

Last updated

Was this helpful?