75142913在线留言
GO语言面向对象2:接口interface详解_Go语言_网络人

GO语言面向对象2:接口interface详解

Kwok 发表于:2020-10-05 12:05:34 点击:46 评论: 0

Go 语言的接口在与其它编程语言比较中是一个比较有特色的方式,在面向对象中是核心中的核心,在项目开发中,接口无所不在,所以要掌握好接口开发,任何其他类型只要实现了这些方法就是实现了这个接口(耦合性低)。这样做的好处在实际项目中,架构师只需要定义一个接口,让不同程序员去实现接口里的方法就可以了,如果发现有问题,在修改了方法也不会影响整个程序的运行,在DEBUG时候不会出现方法关联的情况。极大的减少了耦合性。下现用接口代码模拟一下手机充电。

//定义一个Type-c的接口
type TypeC interface {
	Insert(Charger) //插入数据线,可以传参数进去哦~
	Charge()        //开始充电
	DialOut()       //拨出数据线
}

//定义一个手机
type Phone struct {
	BrandName string //手机的品牌名字
}
//绑定方法到手机并实现接口里的Insert(),按照接口参数传入
func (s Phone) Insert(c Charger) {
	fmt.Print(c.ChargerBrand, "Type-c充电线插入到", s.BrandName, "手机了...")
}

//绑定方法到手机并实现接口里的Charge()
func (s Phone) Charge() {
	fmt.Print(s.BrandName, "手机检测到电流进入,正在充电中...")
}

//绑定方法到手机并实现接口里的DialOut()
func (s Phone) DialOut() {
	fmt.Print(s.BrandName, "手机未检测到电流,充电线已拨出...")
}

/*
Phone必须同时实现interface里定义的所有方法,才算实现了这个接口。
*/

//定义一个充电器
type Charger struct {
	ChargerBrand string
}

//给充电器结构体绑定一个充电方法,参数是一个接口类型
func (c Charger) StartCharge(T TypeC) {
	T.Insert(c) //插入数据线
	fmt.Println()
	T.Charge() //开始充电
	fmt.Println()
	T.DialOut() //拨出数据线
}
func main() {
	Phone := Phone{"小米"}        //定义一个手机对象
	Charger := Charger{"杂牌充电器"} //定义一个充电器对象
	Charger.StartCharge(Phone)  //这里的Phone已实现了所有接口里的方法
}

/*
杂牌充电器Type-c充电线插入到小米手机了...
小米手机检测到电流进入,正在充电中...
小米手机未检测到电流,充电线已拨出...
*/

GO语言的接口(interface)里的只能定义方法,这些方法可以不需要实现,但是只要有结构体实现了接口里的方法,也就是实现了这个接口。接口里不能包含任何的变量和常量

//接口里的method自己定义一系列的方法,可以不去实现这些方法,但只要有对象实现了这些方法就说明这个对象实现了这个接口。
type 接口名 interface{
	method1(参数列表)返回值列表
	method2(参数列表)返回值列表
	...
}

GOlang里的接口都是隐式实现,只要有一个变量(对象实例化),并且变量里包括了接口的所有方法,那么这个变量就实现了这个接口,所以没有implement这样的关键字。GO语言只关心接口里有什么方法,并不关心这个接口叫什么名字,所以1个变量(对像实例化)可以实现多个接口(手机不光可以有充电接口,也可以有数据传输接口、WIFI接口、蓝牙接口等),这是GO语言区别其它语言的特色。

接口的另类骚操作(接上面代码):

var P = Phone{"苹果手机"}
var iPhone TypeC = P //定义一个TypeC接口类型的变量,并把一个实现了接口的对象传给iPhone
iPhone.Charge()          //苹果手机手机检测到电流进入,正在充电中...

接口本身不能创建实例,但可以指向一个实现了该接口的自定义变量(上面代码里的P),或者匿名对象:var iPhone TypeC = Phone{"苹果手机"} 或者使用推导iPhone := Phone{"苹果手机"},因为Phone这个结构体实现了TypeC 接口,TypeC接口中所有的方法都没有方法体(方法只定义未实现),Phone这个结构体里实现了TypeC方法,所以Phone实现了TypeC 接口。

 只要是自定义的数据类型,就可以实现接口,所以有的项目中有时候会看到一个不是结构体也能实现接口的操作,这里做为一个知识点了解一下:

//定义接口
type X interface {
	Print(s string)
}
//定义一个非结构体类型
type MyInt int
//给MyInt绑定一个Print()方法来实现X接口
func (i MyInt) Print(s string) {
	fmt.Println("我是", s, "MyInt里的Print()方法")
}
func main() {
	var a MyInt
	a.Print("a") //我是 a MyInt里的Print()方法
	var b X = a  //把a交给X接口类型的b,因为a实现了X接口
	b.Print("b") //b就可以调用接口里的方法了,打印:我是 b MyInt里的Print()方法
}

 GO语言中的接口(interface)默认是一个指针(引用类型),如果没有对interface初始化就使用会输出nil,空接口interface{}没有任何方法,所以所有的自定义类型都实现了空接口。我们可以把任何类型的变量赋值给空接口:

var B interface{} = 8.8 //可以把任何类型的值赋给空接口
fmt.Println(B)          //打印:8.8

B = "字符串"               //把string类型赋给空接口B
fmt.Println(B)          //打印:字符串

var C int = 10
B = C          //把int类型C赋给空接口B
fmt.Println(B) //打印:10

var D [2]string = [2]string{"数组1", "数组2"}
B = D          //把数组类型D赋给空接口B
fmt.Println(B) //打印:[数组1 数组2]

明明是强类型语言的GO,秒变像PHP一样的弱类型语言了~在实际项目中空接口使用得非常非常多,而且也非常的实用。

上面代码演示了空接口B可以接收任意类型的值,但我们需要把空接口B赋值回去给A、C、D的时候就会出问题。这时候我们就需要使用到类型断言方式,代码演示:

var A float32 = 1.1
var B interface{} = A + 1
//A = B //直接赋值会报错:cannot use B (type interface {}) as type float32 in assignment: need type assertion

A = B.(float32) //使用断言来处理
fmt.Println(A)  //打印:2.1

//使用类型断言需要知道空接口数据的真实类型,如果搞错了也会报错误
ok, err := B.(float64) //断言成功后err返回true,OK返回B的值,否则返回nil,flase
if err {
	fmt.Println(ok)
} else {
	fmt.Println("数据断言的类型与空接口B不匹配")
}
//类型断言的实例在本文最后代码里有演示

接口实现的小陷阱:

type T interface {
	Afunc()
}
type A struct{}

//要程序正常运行需要改为A而不是*A
func (a *A) Afunc() {
	fmt.Println("我是*A的方法")
}
func main() {
	var a = A{}
	a.Afunc() //我是*A的方法

	//下面代码会报错
	var t T = a //*A传入的是指针值,所以A并没有实现Afunc()方法,即A未实现T接口。
	t.Afunc()   //cannot use a (type A) as type T in assignment:A does not implement T (Afunc method has pointer receiver)

	//下面是正确调用方式
	var t T = &a //把a的内存地址传给接口t即可
	t.Afunc()    //我是*A的方法
}

接口编程经常用来的sort排序示例func Sort(data interface):

sort传入的参数要求使用了data接口的对象(变量)进去就可以对这个对象(变量)进行排序操作,根据官方文档里date的接口定义代码如下:

type data interface {
	Len() int           //要求返回对象的长度return(len(对象))
	Less(i, j int) bool //通过i与j的对比,返回一个bool值。return i > j 达到升序/降序
	Swap(i, j int)      //交换i与i的值:i,j=j,i
}

根据官方文档函数sort传入是一个实现了上面代码的接口切片对象就可以进行排序操作,下面是代码演示:

import (
	"fmt"
	"math/rand" //用于下面年龄生成随机数
	"sort"
)
//定义一个学生的结构体
type Student struct {
	Name string
	Age  int
}

type StuSlice []Student //定义一个Student结构体的切片类型

//下面开始来实现sort里要求的3个切片方法
func (s StuSlice) Len() int {
	return len(s) //返回对象s的长度
}

//实现Less()方法
func (s StuSlice) Less(i, j int) bool {
	return s[i].Age > s[j].Age //按年龄降序排列
	//return s[i].Age > s[j].Age //按年龄升序排列
	//return s[i].Name> s[j].Name //按名字降序排列
	//return s[i].Name < s[j].Name //按名字升序排列
}

//实现Swap()方法
func (s StuSlice) Swap(i, j int) {
	s[i], s[j] = s[j], s[i] //交换值
}

func main() {
	var stu StuSlice //定义stu为StuSlice类型(Student的切片)
	for i := 1; i < 10; i++ {
		stu = append(stu, Student{Name: fmt.Sprintf("张%d", i), Age: 10 + i + rand.Intn(10)}) //追加格式化后的数据给stu切片
	}
	fmt.Println("未进行排序的结果")	
	/*未进行排序的结果
	{张1 12}
	{张2 19}
	{张3 20}
	{张4 23}
	{张5 16}
	{张6 24}
	{张7 22}
	{张8 18}
	{张9 25}*/
	for _, v := range stu {
		fmt.Println(v)
	}
	sort.Sort(stu) //通过Sort(data interface)排序,要求stu实现data接口

	fmt.Println("通过Sort排序的结果")
	for _, v := range stu {
		fmt.Println(v)
	}
	/*通过Sort排序的结果
	{张9 25}
	{张6 24}
	{张4 23}
	{张7 22}
	{张3 20}
	{张2 19}
	{张8 18}
	{张5 16}
	{张1 12}*/
}

 看到接口你会发现,我不定义接口也可以实际结构体里的方法,定义了接口到底有什么用处。为了更好的理解接口,我们可以先看下面的图:

GO语言面向对象2接口interface详解

接口可以有效的管理我们的项目,假设可以将接口理解为达到目标所需要的条件,只要达到了目标所设置条件就实际了目标,所以我们开发一个项需要完成什么样的功能为目标,如果数据库基本目标就是增加、删除、修改、查找。

接口和继承解决的问题不同,继承主要价值在于解决代码的复用性和可维护性,而接口要解决的是代码的规范问题。

接口比继承更加灵活,有一定程序上实现了代码的解耦。例如下面的多态数组代码就需要使用接口类型来实现:

type Kids interface {
	Say()
}

//定义一个男孩子的结构体
type Boy struct {
	sex string
}
//实现Kids接口
func (b Boy) Say() {
	fmt.Println("我是:", b.sex, "孩子")
}

//定义女孩子
type Gril struct {
	sex string
}
//实现Kids接口
func (g Gril) Say() {
	fmt.Println("我是:", g.sex, "孩子")
}
//Gril特有的Dance()方法
func (g Gril) Dance() {
	fmt.Println(g.sex, "孩子会跳舞~")
}

func main() {
	var Children [2]Kids    //定义一个Kids接口类型的数组
	Children[0] = Boy{"男"}  //将boy存进数组
	Children[1] = Gril{"女"} //将gril存进数组
	//循环调用数据里的对象
	for _, v := range Children {
		v.Say()               //我是: 男 孩子;我是: 女 孩子
		gril, err := v.(Gril) //利用类型断言判断数据数据类型
		if err {
			gril.Dance() //Dance是Gril特有的方法,Boy是没有的,我们必须要通过断言来判断
		}

	}
	//单独调用数组里的对象
	Children[0].Say() //我是: 男 孩子
	Children[1].Say() //我是: 女 孩子
}
/*
我是: 男 孩子
我是: 女 孩子
女 孩子会跳舞~
我是: 男 孩子
我是: 女 孩子
*/

类型断言在实际的项目开发中会用到的示例:

//定义一个WhatType函数,可传入多个interface参数,上面有讲到所有数据类似都是空接口
func WhatType(myvar ...interface{}) {
	for i, v := range myvar {
		switch v.(type) { //v.(type)后面的.(type)是固定写法,只能用于switch
		case bool:
			fmt.Println("第", i, "个参数是bool类型,值为:", v)
		case float32:
			fmt.Println("第", i, "个参数是float32类型,值为:", v)
		case float64:
			fmt.Println("第", i, "个参数是float64类型,值为:", v)
		case string:
			fmt.Println("第", i, "个参数是string类型,值为:", v)
		case int:
			fmt.Println("第", i, "个参数是int类型,值为:", v)
		case *int:
			fmt.Println("第", i, "个参数是*int指针类型,值为:", v)
		case struct{}:
			fmt.Println("第", i, "个参数是struct类型,值为:", v)
		default:
			fmt.Println("第", i, "个参数是自定义的类型,值为:", v)
		}
	}
}
func main() {
	var a *int
	var b interface{}
	WhatType(1, float32(1.0), "hello", float64(1.0), true, struct{}{}, b, a)
}

/*
第 0 个参数是int类型,值为: 1
第 1 个参数是float32类型,值为: 1
第 2 个参数是string类型,值为: hello
第 3 个参数是float64类型,值为: 1
第 4 个参数是bool类型,值为: true
第 5 个参数是struct类型,值为: {}
第 6 个参数是自定义的类型,值为: 
第 7 个参数是*int指针类型,值为: 
*/
除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/79
标签:GO接口interfaceKwok最后编辑于:2020-10-05 19:05:46
0
感谢打赏!

《GO语言面向对象2:接口interface详解》的网友评论(0)

本站推荐阅读

热门点击文章