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}*/
}
看到接口你会发现,我不定义接口也可以实际结构体里的方法,定义了接口到底有什么用处。为了更好的理解接口,我们可以先看下面的图:
接口可以有效的管理我们的项目,假设可以将接口理解为达到目标所需要的条件,只要达到了目标所设置条件就实际了目标,所以我们开发一个项需要完成什么样的功能为目标,如果数据库基本目标就是增加、删除、修改、查找。
接口和继承解决的问题不同,继承主要价值在于解决代码的复用性和可维护性,而接口要解决的是代码的规范问题。
接口比继承更加灵活,有一定程序上实现了代码的解耦。例如下面的多态数组代码就需要使用接口类型来实现:
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语言面向对象2:接口interface详解》的网友评论(0)