Golang中接口的理解和应用

什么是接口

在Golang中,一个接口是一组方法签名。当一个类型定义了接口里所有定的方法时,就说这个类型实现了这个接口。接口指定类型应该具有的方法,类型决定如何实现这些方法。

package main

import "fmt"

// define interface
type VowelsFinder interface {
	FindVowels() []rune
}

type MyString string

func (ms MyString) FindVowels() []rune {
	var vowels []rune
	for _, x := range ms {
		if x == 'a' || x == 'e' || x == 'i' || x == 'o' || x == 'u' {
			vowels = append(vowels, x)
		}
	}
	return vowels
}

func main() {
	name := MyString("Sam Anderson") // force convert to MyString type
	var v VowelsFinder
	v = name
	fmt.Printf("Vowels are %c", v.FindVowels())
}

在上面的程序中,创建了一个接口类型名为 VowelsFinder,它有一个方法 FindVowels() []rune

通过添加一个方法 FindVowels() []rune 给接受者类型 MyString。现在可以说 MyString 实现了 VowelsFinder 接口。在 Golang 中,如果一个类型包含了一个接口声明的所有方法,那么这个类型就隐式地实现了这个接口。

MyString 类型的变量 name 赋值给 VowelsFinder 类型的变量 v。这是合法的,因为 MyString 实现了 VowelsFinderv.FindVowels()MyString 上调用 FindVowels 方法打印字符串 Sam Anderson 中所有的元音。

空接口

一个没有声明任何方法的接口称为空接口。空接口表示为 interface{}。因为空接口没有方法,因此所有类型都实现了空接口。

package main

import "fmt"

func describe(i interface{}) {
	fmt.Printf("Type = %T, value = %v\n", i, i)
}

func main() {
	s := "Hello World"
	describe(s)
	i := 55
	describe(i)
	strt := struct {
		name string
	}{
		name: "Naveen R",
	}
	describe(strt)
}

上面的程序,describe(i interface{}) 函数接受一个空接口作为参数,因此任何值都可以传递给它。

接口内部表示

一个接口可以被认为是由一个元组内部表示的 (type, value)type 是接口的具体类型,value 是具体类型的值。

package main

import (
	"fmt"
)

type Test interface {
	Tester()
}

type MyFloat float64

func (m MyFloat) Tester() {
	fmt.Println(m)
}

func describe(t Test) {
	fmt.Printf("Interface type %T value %v\n", t, t)
}

func main() {
	var t Test
	f := MyFloat(89.7)
	t = f
	describe(t)
	t.Tester()
}

Test 接口提供了一个方法 Tester()MyFloat 类型实现了这个接口。将 MyFloat 类型的变量 f 赋值给 Test 类型的变量 t 。现在 t 的具体类型是 MyFloat 而它的值是 89.7。

接口的零值

接口的零值是 nil。一个 nil 接口的底层类型和值都是 nil

package main

import "fmt"

type Describer interface {
	Describe()
}

func main() {
	var d1 Describer
	if d1 == nil {
		fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1)
	}
}

如果试图在一个 nil 接口上调用方法,程序将会触发 panic,因为 nil 接口既没有底层的值,也没有具体的类型。

package main

type Describer interface {
	Describe()
}

func main() {
	var d1 Describer
	d1.Describe()
}

上面的程序,因为 d1nil。程序将在运行时触发 panicruntime error: invalid memory address or nil pointer dereference

实现多个接口

一个类型可以实现多个接口。

package main

import "fmt"

type SalaryCalculator interface {
	DisplaySalary()
}

type LeaveCalculator interface {
	CalculateLeavesLeft() int
}

func (e Employee) DisplaySalary() {
	fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}

func (e Employee) CalculateLeavesLeft() int {
	return e.totalLeaves - e.leavesTaken
}

type Employee struct {
	firstName   string
	lastName    string
	basicPay    int
	pf          int
	totalLeaves int
	leavesTaken int
}

func main() {
	e := Employee{
		firstName:   "Naveen",
		lastName:    "Ramanathan",
		basicPay:    5000,
		pf:          200,
		totalLeaves: 30,
		leavesTaken: 5,
	}

	var s SalaryCalculator = e
	s.DisplaySalary()
	var l LeaveCalculator = e
	fmt.Println("\nLeaves left =", l.CalculateLeavesLeft())
}

嵌入接口

package main

import (
	"fmt"
)

type SalaryCalculator interface {
	DisplaySalary()
}

type LeaveCalculator interface {
	CalculateLeavesLeft() int
}

type EmployeeOperations interface {
	SalaryCalculator
	LeaveCalculator
}

type Employee struct {
	firstName   string
	lastName    string
	basicPay    int
	pf          int
	totalLeaves int
	leavesTaken int
}

func (e Employee) DisplaySalary() {
	fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}

func (e Employee) CalculateLeavesLeft() int {
	return e.totalLeaves - e.leavesTaken
}

func main() {
	e := Employee{
		firstName:   "Naveen",
		lastName:    "Ramanathan",
		basicPay:    5000,
		pf:          200,
		totalLeaves: 30,
		leavesTaken: 5,
	}
	var empOp EmployeeOperations = e
	empOp.DisplaySalary()
	fmt.Println("\nLeaves left =", empOp.CalculateLeavesLeft())
}

类型断言

类型断言用来提取接口的实际类型的值。

语法:i.(T),用来获取接口 i 的实际类型 T 的值。

package main

import (
	"fmt"
)

func assert(i interface{}) {
	s := i.(int) //get the underlying int value from i
	fmt.Println(s)
}
func main() {
	var s interface{} = 56
	assert(s)
}

变量 s 的实际类型是 int。使用 i.(int) 来获取 iint 值。

如果实际类型不是 int,那么上面的程序会发生什么?

package main

import (
	"fmt"
)

func assert(i interface{}) {
	s := i.(int)
	fmt.Println(s)
}
func main() {
	var s interface{} = "Steven Paul"
	assert(s)
}

在上面的程序中,将实际类型为 string 的变量 s 传递给 assert 函数,assert 函数尝试从其中提取出一个 int 值。该程序会触发 panicinterface conversion: interface {} is string, not int

为了解决以上问题,可以使用下面的语法:

v, ok := i.(T)

如果接口 i 的具体类型是 T,则 v 将具有 i 的实际值,ok 为 true。

如果接口 i 的具体类型不是 T,则 ok 为 false, vT 的零值,但程序不会触发 panic

package main

import (
	"fmt"
)

func assert(i interface{}) {
	v, ok := i.(int)
	fmt.Println(v, ok)
}
func main() {
	var s interface{} = 56
	assert(s)
	var i interface{} = "Steven Paul"
	assert(i)
}

当把字符串 Steven Paul 传递给 assert 函数,ok 将是 false,因为 i 的实际类型不是 intv 的值将是 0(int 的零值)。

Type Switch

类型分支(type switch)用来将一个接口的具体类型与多个 case 语句指定的类型进行比较。在类型断言 i.(T) 中,将类型 T 替换为关键字 type 就变成了 type switch。

package main

import (
	"fmt"
)

func findType(i interface{}) {
	switch i.(type) {
	case string:
		fmt.Printf("I am a string and my value is %s\n", i.(string))
	case int:
		fmt.Printf("I am an int and my value is %d\n", i.(int))
	default:
		fmt.Printf("Unknown type\n")
	}
}
func main() {
	findType("Naveen")
	findType(77)
	findType(89.98)
}

也可以将类型与接口进行比较。如果我们有一个类型,并且如果这个类型实现了一个接口,就可以将这个类型与它实现的接口进行比较。

package main

import "fmt"

type Describer interface {
	Describe()
}
type Person struct {
	name string
	age  int
}

func (p Person) Describe() {
	fmt.Printf("%s is %d years old", p.name, p.age)
}

func findType(i interface{}) {
	switch v := i.(type) {
	case Describer:
		v.Describe()
	default:
		fmt.Printf("unknown type\n")
	}
}

func main() {
	findType("Naveen")
	p := Person{
		name: "Naveen R",
		age:  25,
	}
	findType(p)
}

标题:Golang中接口的理解和应用
作者:reyren
地址:https://www.reyren.cn/articles/2021/07/08/1625726141724.html

    评论
    0 评论
avatar

取消