一、感受接口
type Usb interface { Connect() Disconnect() } // 手机 type Phone struct {} // 相机 type Camera struct {} // 计算机 type Computer struct {} // 手机实现接口所有方法 func (p Phone) Connect() { fmt.Println("手机连接中...") } func (p Phone) Disconnect() { fmt.Println("手机断开连接中...") } // 相机实现接口所有方法 func (c Camera) Connect() { fmt.Println("相机连接中...") } func (c Camera) Disconnect() { fmt.Println("相机断开连接中...") } // Working方法,接收一个Usb接口类型变量 func (c Computer) Working(usb Usb) { // 通过接口变量来调用Connect和Disconnect方法 usb.Connect() usb.Disconnect() } func main() { // 创建结构体实例 phone := Phone{} camera := Camera{} computer := Computer{} // 关键点 computer.Working(phone) computer.Working(camera) } // 输出如下 // 手机连接中... // 手机断开连接中... // 相机连接中... // 相机断开连接中...
当传入一个phone,usb就能识别是手机,传入camera,usb就能识别是相机,并别分调用其相应的方法。这很明显就是多态呀!!!
func (c Computer) Working(usb Usb) { usb.Connect() usb.Disconnect() } func main() { phone := Phone{} camera := Camera{} computer := Computer{} computer.Working(phone) computer.Working(camera) }
二、接口介绍
interface 类型可以定义一组方法,但是这些方法不需要实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。
语法:
type 接口名 interface { method1(参数列表) 返回值列表 method2(参数列表) 返回值列表 ... }
三、注意事项和细节
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的实例;
- 接口中所有的方法都没有方法体,即只定义方法,没有实现该方法;
- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例赋给该接口类型;
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型;
- 一个自定义类型可以实现多个接口;
- golang的接口不能有任何变量;
- 一个接口(比如 C 接口)可以继承多个别的接口(比如 A、B 接口),这时如果要实现 C 接口,也必须将 A、B 接口中的方法也实现;注意,A、B接口中不能有相同的方法,相当于 C 接口有两个相同的方法,这是不允许的;
- interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil ;
- 空接口 interface{} 没有任何方法,因此所有类型都实现了空接口,即可以把任何一个变量赋给空接口;
- 如果一个自定义类型是使用指针方式实现一个接口的,那么需要将该自定义类型的地址赋给该接口,不然会报错。
type AInterface interface { Eat() } type Person struct { Name string } func (p Person) Eat() { fmt.Printf("%s正在吃饭... ", p.Name) // 佩奇正在吃饭... } func main() { p := Person{"佩奇"} var a AInterface = p // 接口可以指向一个实现了该接口的自定义类型实例 a.Eat() }
type AInterface interface { Eat() } type BInterface interface { Sleep() } type Person struct { Name string } func (p Person) Eat() { fmt.Printf("%s正在吃饭... ", p.Name) // 佩奇正在吃饭... } func (p Person) Sleep() { fmt.Printf("%s正在睡觉... ", p.Name) // 佩奇正在睡觉... } func main() { p := Person{"佩奇"} var a AInterface = p var b BInterface = p a.Eat() b.Sleep() }
type AInterface interface { Eat() } type BInterface interface { Sleep() } type CInterface interface { AInterface BInterface Study() } type Person struct { Name string } func (p Person) Eat() { fmt.Printf("%s正在吃饭... ", p.Name) // 佩奇正在吃饭... } func (p Person) Sleep() { fmt.Printf("%s正在睡觉... ", p.Name) // 佩奇正在吃饭... } func (p Person) Study() { fmt.Printf("%s正在学习... ", p.Name) // 佩奇正在学习... } func main() { p := Person{"佩奇"} var c CInterface = p c.Eat() c.Sleep() c.Study() }
type T interface {} type Integer int func main() { var int1 Integer int2 := Integer(100) var t T t = int1 fmt.Println(t) // 0 t = int2 fmt.Println(t) // 100 }
type A interface { Eat() } type Person struct { Name string } // 使用 Person 指针类型实现一个接口的方法 func (p *Person) Eat() { fmt.Printf("%s正在吃饭...", p.Name) } func main() { p := Person{"佩奇"} //var a A = p // 错误!!!因为Person类型没有实现A接口,修改如下 var a A = &p a.Eat() }
四、接口最佳实践
实现对 Student 结构体切片的排序:sort.Sort(data Interface)
package main import ( "fmt" "math/rand" "sort" "time" ) type Student struct { Name string Score int } type StudentSlice []Student func (ss StudentSlice) Len() int { return len(ss) } // Less方法决定使用什么标准进行排序 // 这里按成绩从小到大排序 func (ss StudentSlice) Less(i, j int) bool { return ss[i].Score < ss[j].Score } func (ss StudentSlice) Swap(i, j int) { ss[i], ss[j] = ss[j], ss[i] } func main() { var ss StudentSlice rand.Seed(time.Now().UnixNano()) for i := 0; i < 10; i++ { student := Student{ Name: fmt.Sprintf("学生%d号", i), Score: rand.Intn(100), } ss = append(ss, student) } // 排序前的顺序 for _, v := range ss { fmt.Println(v) } // 调用sort.Sort方法 sort.Sort(ss) fmt.Println() // 排序后的顺序 for _, v := range ss { fmt.Println(v) } }
输出结果:
五、接口与继承的区别
package main import "fmt" type Monkey struct { Name string } type LittleMonkey struct { Monkey // 继承 } type BridAble interface { Flying() } type FishAble interface { Swimming() } func (m *Monkey) Climbing() { fmt.Printf("%s天生就会爬树... ", m.Name) } func (ls *LittleMonkey) Flying() { fmt.Printf("%s通过学习,会飞翔... ", ls.Name) } func (ls *LittleMonkey) Swimming() { fmt.Printf("%s通过学习,会游泳... ", ls.Name) } func main() { wk := LittleMonkey{Monkey{"小猴子"}} wk.Climbing() wk.Flying() wk.Swimming() }
代码小结:
- 当 B 结构体继承了 A 结构体,那么 B 结构体就"拥有"了 A 结构体的字段和方法,并且可以直接使用;
- 当 B 结构体需要扩展功能,同时不希望去破坏继承关系,则实现某个接口即可。因此可以认为:实现接口可以看做是对继承的一种补充。
接口与继承总结:
①接口与继承解决的问题不同
- 继承的价值主要在于:解决代码的复用性和可维护性。
- 接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
②接口比继承更加灵活
- 继承是 is - xx 的关系,而接口是 like - xx 的关系。为什么这样说? --> 我们不能说猴子是一条鱼,但可以说:猴子可以像鱼儿一样游泳。
③接口在一定程度上实现代码解耦