BeginMan blog

鸭子类型

WIKI讲鸭子类型 已经很清晰了,我接触这个概念是在看雨痕大神的《Go语言学习笔记》一书得来的,作者再讲Golang的接口类型说就是Duck type。

图片来源:嘎嘎叫的小狗——快乐的鸭子类型

如下代码实现go的接口

package main

import (
	"fmt"
)

type User struct {
	name string
	age  byte
}

type Manager struct {
	User
	title string
}

func (u User) ToString() string {
	return fmt.Sprintf("%+v", u)
}

func (m Manager) PrintIt() string {
	return "good"
}

// 接口类型
type Print interface {
	PrintIt() string
	ToString() string
}

func main() {
	var admin Manager
	admin.name = "方朋"
	admin.age = 26
	admin.title = "厂长"
	fmt.Println(admin.ToString())

  // 只要包含接口所需的全部方法,即实现了该接口
	var p Print = admin
	fmt.Println(p.PrintIt())
	fmt.Println(p.ToString())
}

鸭子类型本质体现的是面向接口的编程,对于Go来说,接口是一种契约,是多个方法的集合。就像鸭子一样,有叫、跑、游泳的方法我们说这是鸭子,对于Go,接口实现比较简洁,只要目标类型方法集内包含接口声明的全部方法,就称实现了该接口,不用做显示声明。

对于OOP来说,体现的是面向继承的编程,但”鸭子类型”像多态一样工作,只是没有继承。

“鸭子类型”的语言是这么推断的:走起来像鸭子、游起泳来像鸭子、叫起来也像鸭子,那它就可以被当做鸭子。也就是说,它不关注对象的类型,而是关注对象具有的行为(方法)。

上述就是鸭子类型的表现,也就是任何拥有这样的正确的”走”和”叫”方法的对象都可被函数接受的这种行为。

如下Python代码演示:

class Duck(object):
    def quack(self):
        print("鸭子呱呱叫")

    def swim(self):
        print("鸭子游泳")


class Person(object):
    def quack(self):
        print("这人模仿鸭子呱呱叫")

    def swim(self):
        print("这人模仿鸭子游泳")


def interfaces(duck):
    """
    定义一个接口, 约定呱呱叫和游泳的契约
    接收一个对象, 看这个对象所拥有的方法存不存在,不存在触发异常
    如果都存在, 不管是人还是真鸭子, 都被认为是鸭子
    :param duck:
    :return:
    """
    duck.quack()    # 呱呱叫
    duck.swim()     # 游泳


def game():
    donald = Duck()     # 唐老鸭
    jack = Person()     # Jack
    interfaces(donald)
    interfaces(jack)

game()
# out print:
# 鸭子呱呱叫
# 鸭子游泳
# 这人模仿鸭子呱呱叫
# 这人模仿鸭子游泳

在Python中,鸭子类型的最典型例子就是类似file(file-like)的类。这些类可以实现file的一些或全部方法,并可以用于file通常使用的地方。例如,GzipFile,cStringIO,套接字(socket)也和文件共同拥有许多相同的方法。然而套接字缺少tell()方法,不能用于GzipFile可以使用的所有地方。这体现了鸭子类型的可伸缩性:一个类似file的对象可以实现它有能力实现的方法,且只能被用于它有意义的情形下。

“鸭子类型”没有任何静态检查,如类型检查、属性检查、方法签名检查等。,一切都靠契约和自觉性,所以在可能会在运行时因为不具备某种特定的方法而抛出异常。

Duck-typing avoids tests using type() or isinstance(). Instead, it typically employs the EAFP (Easier to Ask Forgiveness than Permission) style of programming.

在Python里EAFP原则描述了异常处理的使用。例如相对于检查一个自称为类似Duck的对象是否拥有一个quack()方法(使用if hasattr(mallard, "quack"): ...),人们通常更倾向于用异常处理把对quack的调用尝试包裹起来:

try:
    mallard.quack()
except (AttributeError, TypeError):
    print "mallard并沒有quack()函式"

在Js里和Python一样,都属于动态语言,比较灵活。如下是JS的实现:

function guagua(duck) {
    duck.quack("呱呱叫..");
}

var donald = {quack: function(msg) { console.log(msg) }};
var jack = {name:"Jack", 
            swim: function(e){}, 
            quack: function(msg) { console.log(msg) }
};

> guagua(donald)
呱呱叫..

> guagua(jack)
呱呱叫..

完~