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

内容包括
○ 介绍鸭子类型
○ 多态在python语言中的两种实现方法。
○ 鸭子类型在go语言中通过接口实现
○ 空接口
○ go语言内置接口简介
○ go语言接口数据类型的底层实现
○ go语言接口数据类型的注意点
鸭子类型(ducking typing)
“当你看到一个东西走起来像鸭子,游泳起来鸭子,叫起来也像鸭子,那么这个东西就被可以被称为鸭子”
鸭子类型,可以看成是多态思想的一种形式,我们并不心具体对象的属性或者数据类型,而是重点关心对象的行为,也就是对象包含的方法,我们将多种对象的相似动作抽象出来,形成一个抽象层的接口。
python使用继承实现多态
class Duck():a=1def 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 mainimport "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 Duckvar psyDuck PsyDuckvar littleDuck LittleDuckduck = psyDuckduck.who()duck = littleDuck //对接口重新赋值duck.who()GuessWhoIam(psyDuck)//调用可达鸭的who方法GuessWhoIam(littleDuck)//调用小黄鸭的的who方法}output:i am psyDucki am littleDucki am psyDucki 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 mainimport "fmt"type ComedyError stringfunc (c ComedyError) Error() string {return string(c)}func main() {var err errorerr = ComedyError("test")fmt.Println(err)}
error类型像int或者string一样是一个“预定义标识符”,它不属于任何包。它是“全局块”的一部分,这意味着它在任何地方可用,不用考虑当前包信息。
package mainimport ("fmt""log")type overHeatError float64func (o overHeatError) Error() string {return fmt.Sprintf("overheating by %0.2f degree", o)}func checkTemperature(actual float64, safe float64) error {excess := actual - safeif 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 mainimport "fmt"type Gallon float64func (g Gallon) String() string {return fmt.Sprintf("%0.2f gal", g)}type Liter float64func (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
