<br />简介<br /> <br /> go语言中的接口类型`interface{}`,是go语言对面向接口编程思想的实现。面向接口的编程方式又被称为鸭子类型。所谓鸭子类型,是对一种抽象模式的概括。go语言中的接口数据类型可以看成是一组抽象方法的集合,而接口数据类型作为这组抽象方法的标签。

内容包括
○ 介绍鸭子类型
○ 多态在python语言中的两种实现方法。
○ 鸭子类型在go语言中通过接口实现
○ 空接口
○ go语言内置接口简介
○ go语言接口数据类型的底层实现
○ go语言接口数据类型的注意点
鸭子类型(ducking typing)
“当你看到一个东西走起来像鸭子,游泳起来鸭子,叫起来也像鸭子,那么这个东西就被可以被称为鸭子”
鸭子类型,可以看成是多态思想的一种形式,我们并不心具体对象的属性或者数据类型,而是重点关心对象的行为,也就是对象包含的方法,我们将多种对象的相似动作抽象出来,形成一个抽象层的接口。
python使用继承实现多态
class Duck():
a=1
def who(self):
print("I am an Duck")
class PsyDuck(Duck):
'''可达鸭'''
def who(self):
print("我是可达鸭")
class DonaldDuck(Duck):
'''唐老鸭'''
def who(self):
print("我是唐老鸭")
class LittleDuck(Duck):
'''小黄鸭'''
def who(self):
print("我是小黄鸭")
if __name__ == "__main__":
duck1=PsyDuck()
duck2=DonaldDuck()
duck3=LittleDuck()
duck1.who()
duck2.who()
duck3.who()
output:
我是可达鸭
我是唐老鸭
我是小黄鸭
可以看到,三个对象都继承了同一父类,并重写了父类Who()
方法,这就是多态。
其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度,如下
python用鸭子类型实现多态
def func(obj): //定义一个函数接口,参数为类对象
obj.who() //调用类对象的方法
class PsyDuck:
'''可达鸭'''
def who(self):
print("我是可达鸭")
class DonaldDuck:
'''唐老鸭'''
def who(self):
print("我是唐老鸭")
class LittleDuck:
'''小黄鸭'''
def who(self):
print("我是小黄鸭")
if __name__ == "__main__":
duck1=PsyDuck()
duck2=DonaldDuck()
duck3=LittleDuck()
func(duck1)
func(duck2)
func(duck3)
output:
我是可达鸭
我是唐老鸭
我是小黄鸭
通过对比python实现多态的两种方式,就可以体会到鸭子类型的思想。和第一方式不同,第一种方式是显示的继承,继承之后自然具备了父类对象的方法,不同子类对父类的方法进行重写,就可以实现多态,而鸭子类型的多态,只需要对象实现了接口定义的所有方法,即可使用该接口函数进行调用,接口函数不关心对象的属性,数据类型,只要实现了接口的所有方法,即可实现多态。
接下来看如何在go语言中通过接口实现多态
package main
import "fmt"
type Duck interface {
who()
}
type PsyDuck struct {
Name string
}
func (d PsyDuck) who() {
fmt.Println("i am psyDuck")
}
type LittleDuck struct {
Name string
}
func (d LittleDuck) who() {
fmt.Println("i am littleDuck")
}
func GuessWhoIam(d Duck) {//通过参数为接口函数进行调用
d.who()
}
func main() {
var duck Duck
var psyDuck PsyDuck
var littleDuck LittleDuck
duck = psyDuck
duck.who()
duck = littleDuck //对接口重新赋值
duck.who()
GuessWhoIam(psyDuck)//调用可达鸭的who方法
GuessWhoIam(littleDuck)//调用小黄鸭的的who方法
}
output:
i am psyDuck
i am littleDuck
i am psyDuck
i am littleDuck
在看一个例子,实现音乐播放器,在实现一个收音机,它们都具备播放音乐的功能,如果没有定义统一接口,就会出现下列情形。
为了避免重复造轮子,实现多态的特性。所以进行如下修改,
type Player interface{ //定义统一的播放接口
Play()
Stop()
}
func playList(device magazine.Player,songs []string){
...
}
此时只要实现了 Player 接口定义的 Play() 及Stop()方法的对象,都可以作为参数传递进 playList()函数,并调用对象各自的方法了。
如下图所示:接口的本质是引入一个新的中间层,调用方可以通过接口与具体实现分离,解除上下游的耦合,上层的模块不再需要依赖下层的具体模块,只需要依赖一个约定好的接口。
空接口
内置接口
error接口
type error interface{
Error() string
}
自定义的error数据类型如果具有一个返回string的Error方法,它就满足error接口,
package main
import "fmt"
type ComedyError string
func (c ComedyError) Error() string {
return string(c)
}
func main() {
var err error
err = ComedyError("test")
fmt.Println(err)
}
error类型像int或者string一样是一个“预定义标识符”,它不属于任何包。它是“全局块”的一部分,这意味着它在任何地方可用,不用考虑当前包信息。
package main
import (
"fmt"
"log"
)
type overHeatError float64
func (o overHeatError) Error() string {
return fmt.Sprintf("overheating by %0.2f degree", o)
}
func checkTemperature(actual float64, safe float64) error {
excess := actual - safe
if excess > 0 {
return overHeatError(excess)
}
return nil
}
func main() {
var err error = checkTemperature(127, 100)
if err != nil {
log.Fatal(err)
}
}
Stringer接口
type Stringer interface{
String() string
}//使用该接口自定义输出的样式
package main
import "fmt"
type Gallon float64
func (g Gallon) String() string {
return fmt.Sprintf("%0.2f gal", g)
}
type Liter float64
func (l Liter) String() string {
return fmt.Sprintf("%0.2f L", l)
}
func main() {
fmt.Println(Gallon(12.00))
fmt.Println(Liter(10.0))
}
类型断言
接口具体底层原理。
https://www.bookstack.cn/read/aceld-golang/4%E3%80%81interface.md