go 接口基础
在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。
在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
以python中鸭子类型为例: 类中如果有鸭子的走路的方法,鸭子说话的方法,无论你是什么类,你都是鸭子这种类型。
class Duck: # 定义鸭子类型
def speak(self):
pass
def run(self):
pass
class Cat: # 虽然没有继承Duck类,但是Cat就是Duck这一类型
def speak(self):
pass
def run(self):
pass
通俗点说:不需要明面上的继承,只要你有Duck类中的方法,你就是Duck类的子类
在go语言中,也是有鸭子类型的概念,我们称之为接口(interface)
接口的定义
- 接口实际上就是一种类型
// 定义接口 接口就是一系列方法的集合 但是没有实现 需要对象自己去实现
type Duck interface {
speak(s string)
run()
}
接口的实现与使用
func main() {
tduck := TDuck{"唐老鸭1号", 4}
tduck.speak("最呆的") // 唐老鸭说: 我的名字是 唐老鸭1号 我是 最呆的
tduck.run() // 唐老鸭1号 走路歪歪扭扭
}
// 定义结构体
type TDuck struct {
name string
age int
}
type PDuck struct {
name string
age int
}
// 唐老鸭,实现speak和run
func (t TDuck) speak(s string) {
fmt.Println("唐老鸭说: 我的名字是 ", t.name, "我是", s)
}
func (t TDuck) run() {
fmt.Println(t.name,"走路歪歪扭扭")
}
// 普通鸭子,实现speak和run
func (t PDuck)speak(s string) {
fmt.Println("我是普通鸭子,我的名字是:",t.name,"我说:",s)
}
func (t PDuck)run() {
fmt.Println("我是普通鸭子,我的名字是:",t.name,"我走路歪歪扭扭")
}
需要注意的是,在go中接口的实现, 必须实现接口中所有的方法,才能出现上图中的标识,哪怕只有一个方法没有实现,都不算实现接口。
接口的实际用途
TDuck实现了Duck接口,所以TDuck的对象都是Duck类型的对象,可以赋值给Duck类型的对象,但是只能使用接口中的方法,不能使用具体类型的属性。
func main() {
tduck := TDuck{"唐老鸭1号", 4}
tduck.speak("最呆的") // 唐老鸭说: 我的名字是 唐老鸭1号 我是 最呆的
tduck.run() // 唐老鸭1号 走路歪歪扭扭
test5(tduck,"最呆的") // 传参
var duck Duck
duck = tduck // 直接赋值
}
func test5(d Duck, s string) {
d.run() // 唐老鸭说: 我的名字是 唐老鸭1号 我是 最猛的
d.speak(s) // 唐老鸭1号 走路歪歪扭扭
}
空接口与匿名空接口
没有包含方法的接口称为空接口。空接口表示为 interface{}
。由于空接口没有方法,因此所有类型都实现了空接口。
type Panada interface {}
func PrintPa(i Panada) {
fmt.Printf("Type = %T, value = %v\n", i, i)
}
func main() {
PrintPa(10) // Type = int, value = 10
PrintPa("lxx") // Type = string, value = lxx
PrintPa([3]int{1,2,3}) // Type = [3]int, value = [1 2 3]
}
匿名空接口,很简单就是没有名字的空接口,只使用一次,写法如下
func PrintPa(i interface {}) {
fmt.Printf("Type = %T, value = %v\n", i, i)
}
类型断言
类型断言用于提取接口的底层值,在语法 i.(T)
中,接口 i
的具体类型是 T
,该语法用于获得接口的底层值。用于解决在函数中,需要用到实现该接口的结构体对象的属性时使用。代码如下
func main() {
tduck := TDuck{"唐老鸭1号", 4}
test5(tduck,"最呆的")
}
func test5(d Duck, s string) {
d.run()
d.speak(s)
// 接口对象只有方法,没有具体类型(tduck)的属性
//fmt.Println(d.name) // 报错
v,ok := d.(TDuck)
// ok 为 true时,断言成功,为false时断言失败
// 也可以采用 v := d.(TDuck) 写法,但这种写法一但断言失败就会抛异常,不建议使用
if ok {
fmt.Println(v.name) // 唐老鸭1号
fmt.Println(v.age) // 4
}
}
类型选择
类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。唯一的区别在于类型选择指定的是类型,而一般的 switch 指定的是值。
类型选择的语法类似于类型断言。类型断言的语法是 i.(T)
,而对于类型选择,类型 T
由关键字 type
代替。
func main() {
tduck := TDuck{"唐老鸭1号", 4}
pduck := PDuck{"老鸭1号", 4}
test6(tduck, "最呆的")
test6(pduck, "最蒙的")
}
func test6(d Duck, s string) {
switch obj := d.(type) {
case TDuck:
obj.run()
fmt.Println(obj.name)
case PDuck:
obj.speak(s)
fmt.Println(obj.age)
}
}
补充:
- 接口的零值是 nil 所以接口是引用类型
- 接口的底层实现是通过指针指向了具体的类型(type : value)
type
是接口底层的具体类型(Concrete Type),而value
是具体类型的值。
var duck Duck
fmt.Println(duck) // <nil>