其实对我来说鸭子类型一直是一个比较模糊的概念,因为平常不去关注,关注的时候一般是在面试或者被面试的时候,今天在看一篇博客的时候又有看到,所以就索性弄清楚
```
首先 与duck typing相对应的是normal typing(对象的类型决定对象的特性)
*既然是相对,可以简单理解为 duck typing 就是对象的特性决定了他是什么,说一个烂大街的句子:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”,不是因为它是鸭子所以走路,游泳和叫像鸭子,而是走路,游泳和叫像鸭子所以被``认为``是鸭子
duck typing中对象的类型不重要,只要对象有类型A的方法和属性,那么它被当做类型A来使用。熟悉c的同学应该明白c就是normal typing,在编译阶段静态检查,函数定义和传参类型需要保持一致,不一致就编译报错。参数类型当然也包含了 ``对象`` 参数,后面我会举个Go的例子。
Python中的鸭子类型:
classDuck:deffly(self):print("Duck flying")classAirplane:deffly(self):print("Airplane flying")classWhale:defswim(self):print("Whale swimming")defruntest(obj):
obj.fly()
duck=Duck()
airplane=Airplane()
whale=Whale()
此时:
runtest(duck) √
runtest(airplane) √
runtest(whale) ×
前两个有fly方法,所以在下面的runtest中直接就可以调用,runtest不去理会你传进来的到底是谁的对象,只要你有这个方法,那么我就可以去调用运行。
其实就是多态的一种体现,多态是什么?“多态是指不同类的对象对同一消息作出响应,同一个接口,不同的对象会做出不同的反应;同一个行为可以通过多种表现形式展现出来就是多态”
例如上面同样的runtest这个接口,输入了不同的对象,打印了不同的结果。
在go语言中的 接口:
首先我们定义一个规范,也就是说定义一个接口:
type Duckinterface{
Quack()//鸭子叫
DuckGo() //鸭子走
}
这个接口是鸭子的行为,我们认为,作为一只鸭子,它需要会叫,会走。然后我们再定义一只鸡:
type Chickenstruct{
}
假设这只鸡特别厉害,它也会像鸭子那样叫,也会像鸭子那样走路,那么我们定义一下这只鸡的行为:
func (c Chicken) Quack() {
fmt.Println("嘎嘎")
}
func (c Chicken) DuckGo() {
fmt.Println("大摇大摆的走")
}。*注意,这里只是实现了 Duck 接口方法,并没有将鸡类型和鸭子接口显式绑定。这是一种非侵入式的设计。
然后我们让这只鸡,去叫,去像鸭子那样走路:
func main() {
c :=Chicken{}vard Duck
d=c
d.Quack()
d.DuckGo() # 这一点目前有点疑问,关于接口可以先参照下面的题外话代码
}
执行之后我们可以得到结果:
嘎嘎
大摇大摆的走
这里 go 的func (c Chicken) DuckGo()
func (c Chicken) Quack()
两个函数可以看成是Python中的方法,go利用接口 type Duck interface 实现了go中的多态,如果没有这个interface的存在,可以看出 一个函数的方法,必须要指定入参的类型,例如func (c Chicken) Quack(),指定了这个入参是Chicken类型的,
如果传入其它的,当然就会编译报错
例:
type Vertex struct {
X, Y float64
}
type Vertex2 struct {
X, Y float64
}
func (v *Vertex) test(){
v.X++;
v.Y++;
}
func main(){
v2 := Vertex2{2,3}
v2.test()
}
# awesomeProject
.\test.go:77:3: v.test2 undefined (type Vertex2 has no field or method test)
题外话插一嘴,关于go的接口的,这一部分参考go by exampel:
package main
import ("fmt"
"math")
type geometryinterface{
area() float64
perim() float64
}
type rectstruct{
width, height float64
}
type circlestruct{
radius float64
}
func (r rect) area() float64 {return r.width *r.height
}
func (r rect) perim() float64 {return 2*r.width + 2*r.height
}
func (c circle) area() float64 {return math.Pi * c.radius *c.radius
}
func (c circle) perim() float64 {return 2 * math.Pi *c.radius
}
func measure(g geometry) { # 这里才体现出了接口的作用,入参统一为geometry,所以下面↓
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r) # 这里这里,r和c不是同一个结构体对象,但是可以同时作为入参使用measure函数,并且满足在measure中的g.area()和g.perim()方法
measure(c)
}
```
知识储备有限,很可能会出现错误,欢迎指正,尤其是go语言部分刚刚接触,所以如果有错误请留言??
参考: