接口 Interface概述
- 在Golang中大量存在,面向对象的核心与灵魂一定是接口编程,且相对于其他OOP语言也是极具特色。
- 手机,相机连接计算机USB接口,都是统一制式,不用担心到底什么设备连接计算机,插上什么就是什么,从而引出多态概念。
- 多态主要是依赖接口实现的,低耦合高内聚。
- 接口定义了几个未实现的方法,创建其他结构体,补全各自方法,从而实现不同结构体调用类似"集线器"完成接口所有方法。
- ★★★接口中所有方法都没有方法体,即接口都是未实现的方法,接口体现了程序设计的多态和高内聚低耦合的思想。
- ★★★Golang接口不需要显式的实现,只要一个变量包含接口所有方法,那么变量就实现了这个接口,没有implement关键字。
举例说明,定义一个接口
package main
import "fmt"
type Usb interface {
/*
声明了两个没有实现的方法
*/
Start() //定义接口类型与结构体类似
Stop() //接口类型中只能定义方法!不能定义属性!
}
type Usb2 interface {
/*
声明了两个没有实现的方法
*/
Start()
Stop()
Test() //下面所有结构体方法都没有Test(),没有实现就不可使用Usb2接口,一定会报错,必须要完全实现方法
}
type Phone struct {
}
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作")
}
type Camera struct {
}
func (c Camera) Start() {
fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作")
}
type Computer struct {
}
func (c1 Computer) Connect(usb Usb) {
usb.Start()
usb.Stop()
}
func main() {
//创建结构体变量
computer := Computer{}
phone := Phone{}
camera := Camera{}
//重点
computer.Connect(phone)
computer.Connect(camera)
}
接口应用场景
- 开发过程中不可能由一个程序员完成所有工作,项目经理就可以提供接口,所有接口内容一致,由程序员实现各自负责变量的所有方法。
- 可以很好地控制和管理软件开发进度。
接口的事项和细节
- 接口本身不能创建实例,但可以指向一个实现了该接口的自定义类型的变量。 //上面示例中 Usb接口本身没有实例,但是Phone和Camera可以创建实例使用接口方法
- 接口中所有方法都没有方法体,即都是没有实现的方法。
- Golang中,一个自定义类型需要将某个接口的!所有方法!都实现,我们才说这个自定义类型实现了该接口。
- 只要是自定义类型,都可以实现接口,不仅仅是结构体,int,float都可以的,前提是只要实现了接口所有方法!
- 一个自定义类型可以实现多个接口,只要实现了所有方法! //A接口方法1,B接口方法2,一个结构体变量实现了方法1和方法2,那么A和B中接口全部实现并且可以使用方法。
- 定义接口类型一定不能包含任何变量!只能是未实现的方法!
- 接口也可以继承,继承的前提是实现所有继承被继承的方法!
- 接口是一个引用类型,与结构体不同,如果没有初始化使用,输出会为nil。
- 空接口interface{}没有任何方法,所以所有数据类型都实现了空接口,即我们可以将任何变量赋值给空接口。
- ★★★在实现接口方法时,一定要注意,方法绑定变量 func (p phone) xx() {} 和func (p *phone) xx() {} 是两个完全不一样的概念,一个是phone类型实现方法,一个是phone对应的指针类型实现了方法,后面如果涉及接口变量,一定要对应上,不然肯定编译错误。
针对第九条举例说明,看看空接口接收值打印出来什么样。
package main
import "fmt"
type T1 interface {
}
func main() {
//没有初始化使用,打印出来是nil<nil>
var t1 T1
fmt.Println(t1)
//给空接口传入一个整型
num := 666
var t2 T1 = num
fmt.Println(t2) //666
//直接定义空接口类型,直接实例化出一个t3空接口赋予num1
var num1 = 8.8
var t3 iterface{} = num1
fmt.Println(t3) //8.8
}
接口和继承的关系
- 接口可以通过定义未实现方法,作为继承后的新变量的扩展,例如剑豪继承了剑圣的所有剑法,同时又开眼悟出新的技能,新的技能就可以通过接口方式实现。
- 实现接口对继承机制的补充,接口提供共性规范和标准。
- 继承主要解决代码复用性和可维护性问题;接口主要在于设计好各种规范,让其他自定义类型去实现这种规范方法。
- 接口比继承更加灵活,不需要类似继承是百分百契合not is a,只需要like a即可。
- 接口一定程度上实现了代码解耦。
多态
- 变量具有多种形态,面向对象的第三大特征,Golang中多态是通过接口实现的。
- 可以按照统一的接口调用不同的实现,这时接口变量就呈现不同的形态。
接口体现多态特征
- 多态参数:通过传入形参来决定哪个变量实现接口方法。
- 多态数组:在一个数组中存放多态参数涉及到的各种结构体,其中结构体还有自己独特方法可以被调用,详情见下面断言
断言 assert
-
一个结构体变量赋值给一个空接口,再指定一个同类型结构体变量,将空接口赋值给这个结构体变量,这样直接做是不行的(need type assertion)
package main import "fmt" type Zuobiao struct { x int y int } type A interface { } func main() { zuobiao := Zuobiao{3,5} var a A a = zuobiao fmt.Println(a) var zuobiao1 Zuobiao //zuobiao1 = a //返回来赋值是不可以的, //这时候需要使用断言判断是否可以转换,如果不能转换就会报错,因为a指向zuobiao,所以可以转换成功。 zuobiao1 = a.(Zuobiao) //如果希望把空接口重新转换成原类型,那么就要使用断言 <接口变量>.(<回转类型名称>) fmt.Println(zuobiao1) }
-
类型断言:由于接口是一般类型,不是具体类型,要转换成具体类型,就需要使用类型断言。
-
断言不是胡乱转换,而是空接口变量指向的是什么类型,就必须使用该类型。
-
如何进行断言时进行检查,带上检测机制,如果成功就ok,否则也不会报panic
//带检测类型的断言写法1
var b A var zb2 = Zuobiao{6,7} b = zb2 zb2,ok := b.(Zuobiao) //ok本身是布尔类型,这里是重点 if ok { //接下来条件判断,不会产生panic,而是根据判断继续流程控制 fmt.Println("OK,转换成功",zb2) } else { fmt.Println("转换不成功,继续执行后续代码") } fmt.Println("断言已经判断,可以继续执行")
//带检测类型的断言写法2
var b A var zb2 = Zuobiao{6,7} b = zb2 if zb2,ok := b.(Zuobiao); ok { //使用了if并列条件方式,结合了两句话,开发中也常用。 fmt.Println("OK,转换成功",zb2) } else { fmt.Println("转换不成功,继续执行后续代码") } fmt.Println("断言已经判断,可以继续执行")
案例1:类型断言经常使用的地方:共有接口的结构体变量中有自己独特方法,应该如何保证调用
package main
import "fmt"
type Usb interface {
/*
声明了两个没有实现的方法
*/
Start() //定义接口类型与结构体类似
Stop() //接口类型中只能定义方法!不能定义属性!
}
type Phone struct {
}
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作")
}
func (p Phone) Call() {
fmt.Println("手机可以打电话")
}
type Camera struct {
}
func (c Camera) Start() {
fmt.Println("相机开始工作")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作")
}
type Computer struct {
}
func (c1 Computer) Connect(usb Usb) {
usb.Start()
//如果usb指向是一个phone结构体变量的,还需要使用call()方法
//这时候添加类型断言,这个在今后开发中很重要,常用到
if phone,ok := usb.(Phone) ; ok { //phone对应变量可以随意定义,保证变量与<变量>.Call()一致即可
phone.Call() //手机可以打电话,这种方式可以使每个独特方法的结构体增加美观性。
}
usb.Stop()
}
func main() {
/*
解决共有接口类型的数组结构体又分别有自己独特方法的解决方案
*/
var arrUsb [2]Usb
arrUsb[0] = Phone{}
arrUsb[1] = Camera{}
var c1 Computer
for _ , v := range arrUsb {
c1.Connect(v)
}
}
案例2:写一个函数判定传入数据类型并输出结果,重点在于<数组元素>.(type)这种断言使用方式
package main
import "fmt"
func TypeJudge (items ...interface{}) { //传入可变长度类型的空接口
for i , v := range items { //遍历所有传入参数所在数组
switch v.(type) { //switch case循环判定类型为哪些
case bool:
fmt.Println(i," ",v,"为布尔类型")
case string:
fmt.Println(i," ",v,"为字符串类型")
}
}
}
func main() {
/*
定义一个可变参数的空接口
*/
var name = "shit" //字符串
var buer = false //布尔
TypeJudge(name,buer)
}