接口类型是一种抽象的类型,不会暴露它所代表的内部属性的规范,只会展示出它自己的方法,因此不能将接口类型实例化。
Go的接口是非常灵活的,通过一种方式来声明对象的行为,谁实现了这个行为,就相当于实现了接口里面声明各种方法的集合,但接口本身不去实现这些方法所需要的一些操作,所以说是抽象的方法。
说白了就是接口只声明方法,具体怎么实现由类定义。接口没有数据字段,只有定义的方法。
type Animal interface{
Name() string
Speak() string
}
type Cat struct{
}
func (cat Cat) Name() string{
return "Cat"
}
func (cat Cat) Speak() string{
return "喵喵喵"
}
结构体Cat实现了Animal接口的两个方法,因此我们认为Cat实现了Animal接口
鸭子类型(程序设计风格)著名的鸭子类型:动态类型的一种风格,一个对象有效的语义,不是由继承自特定的类或实现的接口来决定,而是由当前方法和属性的集合决定。
鸭子测试:当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟也可以被称为鸭子。 Go语言通过接口实现了鸭子类型。
提示:一般来说,静态类型语言在编译时便以确定了变量的类型,Go语言的实现是在编译时判断变量的类型。
type 接口名 interface{
Method()
}
type IDatabaser interface{
Connect() error
Disconnect() error
}
type Mysql struct{
DBName string
isConnect bool
}
type Redis struct{
DBName string
}
func (mysql *Mysql) Connect() error {
fmt.Println("Mysql Connct DB =>" + mysql.DBName)
mysql.isConnect = true
if mysql.isConnect {
fmt.Println("Mysql Connct Success!")
}else{
return errors.New("Connct failure!")
}
}
func (mysql *Mysql) Disconnct() error{
fmt.Println("Mysql Disconnect Success!")
return nil
}
func (redis *Redis)Conncet() error{
fmt.Println("Redis Connct DB =>" + mysql.DBName)
fmt.Println("Redis Connct Success!")
return nil
}
func (redis *Redis)Disconnect() error{
fmt.Println("Redis Disconnect Success!")
return nil
}
func main(){
var mysql = Mysql{DBName:"student"}
fmt.Println("开始连接!")
mysql.Connect
fmt.Println("断开连接!")
mysql.Disconnect
var redis := Redis{DBName:"teacher"}
fmt.Println("开始连接!")
redis.Connect
fmt.Println("断开连接!")
redis.Disconnect
}
上面我们定义了接口,但是不断建立新的数据库类型就要实现接口的方法,有很多重复的代码!
面向接口编程,可以抽象出更简洁,更易于管理的代码。 Go多态就是通过接口实现的
func HandleDB(db IDatabaser){
fmt.Println("开始连接!")
db.Connect
fmt.Println("断开连接!")
db.Disconnect
}
func main(){
mysql := Mysql{"DBname":"student"}
HandleDB(&mysql)
redis := Redis("DBName":"teacher")
HandleDB(&redis)
}
HandleDB()函数只有一个,却能实现处理多个不同类型的数据 这也成为GO的多态。
一个类型可以实现多个接口,多个类型可以实现同一个接口。
接口赋值:如果用户自定义类型实现了某个接口类型所声明的一组方法,那么这个用户定义的类型的值就可以赋给一个接口变量。
var 接口变量名 接口类型 = &对象
存在两种赋值情况:
1.对象实例赋值给接口,注意只能将对象的指针赋值给接口变量
2.将一个接口赋值给另一个接口。若两个接口拥有同样的方法集,那么他们就是相同的,可以相互赋值,若接口A的方法集是接口B方法集的子集,那么接口B可以赋值给接口A,反之不成立。
type IDatabaser interface{
Connect() error
Disconnect() error
}
type IRediser interface{
Connect() error
}
type Redis struct{
DBName string
}
func (redis *Redis)Connct() error{
fmt.Println("Redis Connct DB =>" + mysql.DBName)
fmt.Println("Redis Connct Success!")
return nil
}
func (redis *Redis)Disconnect() error{
fmt.Println("Redis Disconnect Success!")
return nil
}
func main() {
var idb IDatabaser = &Redis{DBName:"teacher"}
var iredis IRediser
iredis = idb
iredis.Connect()
]
接口嵌入(继承):非侵入式的风格
侵入式接口:需要显式地创建一个类实现一个接口(例如Java要实现一个接口,不许使用implements显式声明)
非侵入式接口:无需显式地创建一个类实现接口
接口类型只嵌入接口类型,不能嵌入自身和其他数据类型,包括直接嵌入和间接嵌入。
type IPerson interface{
Speak()
}
type IStudent interface{
IPerson
Study()
}
type ITeacher interface{
IPerson
Teach()
}
type Teacher struct{
Name string
}
func (t *Teacher) Speak(){
fmt.Println("My Name is ",t.Name)
}
func (t *Teacher) Teach(){
fmt.Println(t.Name," is teaching!")
}
func main(){
vat teacher = &Teacher{Name:"Mr Li"}
teacher.Speak()
teacher.Teach()
}
ITeacher接口嵌入了IPerson接口,可以理解继承了IPerson接口,都拥有了IPerson接口中的Speak()方法,但是需要结构体Teacher实现这两个方法。
非侵入式好处:1、去掉繁杂的继承体系
2、实现类的时候只需关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理
3、不用为了实现一个接口导入一个包,多引用一个外部的包,意味着更多的耦合。这样更简洁,灵活,注重实用性。提高代码复用率(通过接口赋值实现新的接口)
从空接口取值:不能把空接口赋值到其他类型,若需如此,必须使用类型断言。
func main(){
var a string = "abc"
var i interface = a
var b string = i.(string) //简单断言(一旦不是string,就会抛出panic),建议使用下面的类型断言
fmt.Println(b)
}
//结果为:
abc
类型断言:接口类型向普通类型的转换就是类型断言
语法: t,ok := X.(T)
判断X的类型是否是T,如果断言成功,ok为True,t的值为接口变量X的动态值,失败t为类型T的初始值。
如果断言类型T是一个接口类型,若检查成功,检查结果接口t转换为接口类型T,t的值为接口变量X的动态类型和动态值
这意味着对一个接口类型的断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是他保护了接口值内部的动态类型和值的部分。
type Person interface{
Speak()
}
type Student struct{
Name string
}
func (s Student) Speak(){
fmt.Println("My name is ",s.Name)
}
func checkType(t interface{}, ok bool) {
if ok{
fmt.Println("断言成功")
}else {
fmt.Println("断言失败")
}
fmt.Printf("变量t的类型 = %T,值 = %v n",t ,t)
}
func main(){
var student interface{} = Student{Name:"小明"}
fmt.Println("第一次断言:")
t0,ok := student.(string)
checkType(t0,ok)
fmt.Println("第二次断言:")
t1,ok := student.(Person)
checkType(t1,ok)
}
//结果为:
第一次断言:
断言失败!
变量t的类型 = string,值 =
第二次断言:
断言成功!
变量t的类型 = main.Student,值 = {小明} //空接口变为



