29

Go 语言 接口(Interface)

 5 years ago
source link: https://studygolang.com/articles/14312?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What is Interface type in Go ?

GoLang官网 language specification文档 对interface type的概念说明如下:

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.

Go 语言提供了另外一种数据类型即接口(interface),它把所有的具有共性的方法定义在一起,这些方法只有函数签名,没有具体的实现代码(类似于Java中的抽象函数),任何其他类型只要实现了接口中定义好的这些方法,那么就说 这个类型实现(implement)了这个接口

接口的通用定义方式如下

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

/* 定义xxx数据结构类型 */
type struct_name xxx

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

上面使用type声明一个名为 interface_name 的接口,interface类型是可以定义为变量的,比如

var  name  interface_name

接口在Go语言中是引用类型,因此在上面定义的接口变量中,name是一个指针

我们依照上面的接口通用定义方式,定义以下实例

//定义电话接口
type Phone interface {
   call()
}
//自定义结构体
type Nokia struct {

}
//实现接口方法
func (nokia Nokia)call()  {
   fmt.Println("I am Nokia, I can call you!")
}

func main() {
   var  phone Phone
   phone = new(Nokia)
   phone.call()
}

call()是Phone接口定义好的一个方法,然后由Nokia实现了接口中这个方法,所以Nokia 实现了 Phone接口。

Interface“多态”特性实例

interface{}类型的变量,可以使用任何类型的值来赋值

func main() {
   var a interface{}  = 123
   var b interface{}  = "abc"
   var c interface{}  = 1.23
   fmt.Println(a,b,c)
}
-----output----
123 abc 1.23

在Go语言中自带的标准Packages中,有很多地方利用 interface 来处理未知数据类型,比如我们常用的fmt包,以fmt.Println为例,它的函数签名格式如下

// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) (n int, err error) {
   return Fprintln(os.Stdout, a...)
}

fmt包的Println函数需要传入interface类型的可变长参数,该函数在实现底层的打印行为时,要求传入的可变长参数实现了fmt包中定义的Stringer接口。

Stringer接口类型描述如下:

// Stringer is implemented by any value that has a String method,
// which defines the ``native'' format for that value.
// The String method is used to print values passed as an operand
// to any format that accepts a string or to an unformatted printer
// such as Print.
type Stringer interface {
   String() string
}

所以,自定义类型想要调用fmt.Printf()做格式化打印,那只需实现Stringer接口就行。

举例说明:自定义输出类型格式

定义一个map集合,并循环输出map的元素

type PersonInfo struct {
   ID string
   Name string
   address string
}
func main() {
   //创建集合
   var myMap map[string] PersonInfo
   //初始化集合
   myMap = make(map[string]PersonInfo)
   //向map中添加元素
   myMap ["1"] = PersonInfo{"1","zhangsan","shanghai"}
   myMap ["2"] = PersonInfo{"2","wangwu","beijing"}
   myMap ["3"] = PersonInfo{"3","lisi","tianjin"}
   //循环输出
   for num,person := range myMap{
      fmt.Printf("%v: %v\n",num,person)
   }
}
----------output------------
1: {1 zhangsan shanghai}
2: {2 wangwu beijing}
3: {3 lisi tianjin}

现在我们要自定义map集合输出的格式,比如输出第一个元素:1: {1&zhangsan&shanghai},因此 PersonInfo 需要实现Stringer接口

//自定义类型需要实现Stringer接口
func (pInfo PersonInfo)String()string  {
   return fmt.Sprintf("%v&%v&%v",pInfo.ID,pInfo.Name,pInfo.address)
}

最终自定义格式化输出为:

1: 1&zhangsan&shanghai
2: 2&wangwu&beijing
3: 3&lisi&tianjin

接口嵌入

Go语言的接口对嵌入支持的非常好,接口可以嵌入其他的接口,效果就像在接口中 直接添加被嵌入接口的方法一样。

type HavingFoot interface {
   Foot()
}
type HavingEye interface {
   Eye()
}

type HuMan interface {
   HavingEye
   HavingFoot
}

HuMan接口嵌入了 HavingFoot 接口与 HavingEye 接口,就相当于HuMan接口包含了HavingFoot 接口与 HavingEye 接口中所有的方法。如果要实现HuMan接口,就要定义 Foot() 与 Eye() 方法。

类型猜测

如果有两个类型都实现了同一接口,那么这两个类型变量A,B都可以赋值给这个接口类型的变量C(类似于Java继承中的向上传递),然后这个接口类型的变量C,可以去查找是类型变量A还是B给它赋值的,查询通用格式如下:

value,ok := obj.(struct)

struct:需要猜测的类型A或B value:返回类型变量A或B

ok:查询结果 obj:接口类型的变量C

类型猜测实例

//定义电话接口
type Phone interface {
   call()
}
//自定义结构体
type Nokia struct {

}
type Iphone struct {

}

//实现接口方法
func (nokia Nokia)call()  {
   fmt.Println("I am Nokia, I can call you!")
}

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

func main() {
   //将两个子类赋值给接口
   var phone1 Phone = Nokia{}
   var phone2 Phone = Iphone{}
   //判断phone1是不是Nokia赋值给它的
   if values,ok := phone1.(Nokia);ok {
      values.call()
   }
   //判断phone2是不是Nokia赋值给它的
   if values,ok := phone2.(Iphone);ok {
      values.call()
   }
}

推荐阅读: https://studygolang.com/articles/2652


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK