什么是鸭子打字?

我在网上阅读软件随机主题时遇到了鸭子打字这一术语,并没有完全理解它。

什么是“鸭子打字”?

这是一个用于dynamic语言的术语,没有强大的input 。

这个想法是,你不需要一个types来调用一个对象的现有方法 – 如果一个方法被定义,你可以调用它。

这个名字来源于“如果它看起来像一只鸭子,鸭子像鸭子一样,它是一只鸭子”。

维基百科有更多的信息。

鸭子键入意味着一个操作没有正式地指定操作数必须符合的要求,而只是试着给出它。

与其他人所说的不同,这不一定涉及dynamic语言或inheritance问题。

示例任务:调用某个方法Quack对象。

如果不使用duck-typing,那么执行此任务的函数必须事先指定它的参数必须支持某种方法Quack 。 常用的方法是使用接口

 interface IQuack { void Quack(); } void f(IQuack x) { x.Quack(); } 

调用f(42)失败,但只要donaldIQuacktypes的一个实例, f(donald)可以工作。

另一种方法是结构打字 – 但同样,方法Quack()是正式指定的任何事先不能certificate它的quack将导致编译器故障。

 def f(x : { def Quack() : Unit }) = x.Quack() 

我们甚至可以写

 f :: Quackable a => a -> IO () f = quack 

在Haskell中, Quackabletypes类确保了我们方法的存在。


那么鸭子打字怎么改变这个?

那么,正如我所说,一个鸭子打字系统没有指定要求, 只是尝试,如果任何工作

因此,一个像Python一样的dynamictypes系统总是使用鸭子打字:

 def f(x): x.Quack() 

如果f得到一个支持一个Quack()x ,那么一切都很好,如果不是,它会在运行时崩溃。

但是,鸭子打字并不意味着dynamic打字 – 事实上,有一个非常stream行但完全静态的鸭子打字方法,并没有给出任何要求:

 template <typename T> void f(T x) { x.Quack(); } 

这个函数并没有说明它需要一些可以使用Quack x ,所以它只是在编译时尝试如果一切正常,那就没问题了。

没有代码的解释 – 使用类比

问一个程序员它是什么,你会得到三个不同的答案,但这是大多数人通过鸭子打字的一般想法:

鸭打字

(“如果它像鸭子一样走路,嘎嘎叫鸭子,那么它就是一只鸭子。”) – 是的,但这是什么意思? 举例说明:

示例:dynamictypes的语言

想象一下,我有一个魔杖。 它有特殊的权力。 如果我挥动魔杖说“开车!” 一辆车,那么,它开车!

它在其他事情上工作吗? 不知道:所以我试着在卡车上。 哇 – 它也开车! 然后我在飞机,火车和1个伍兹上试试。 他们都开车!

但是,它会工作,说一个茶杯? 错误:KAAAA-BOOOOOOM! 那工作不太好。 ====>茶杯不能开车! 咄!?

这基本上是鸭子打字的概念。 这是一个先试后买的types系统。 如果有效,一切都很好。 但是,如果失败了,它会炸毁你的脸。

换句话说,我们感兴趣的是客体可以做什么 ,而不是客体是什么

例如:静态types的语言

如果我们关心的是实际的对象 ,那么我们的魔术就只能用于预先设定的授权types – 在这种情况下是汽车,但是在其他可以驾驶的对象上是失败的 :卡车,轻便摩托车,嘟嘟车等等。它不会在卡车上工作,因为我们的魔杖正在期待它只能在汽车上工作

换句话说,在这种情况下,魔棒看起来非常接近目标(是汽车吗?),而不是目标能做什么 (例如汽车,卡车等是否可以驾驶)。

唯一可以让卡车开车的方法是,如果能以某种方式让魔术棒期待卡车汽车(也许是通过“实现一个通用接口”)。 如果你不知道那是什么意思,那就暂时忽略它吧。

概要

要做到这一点,重要的是,鸭子打字的重要性在于对象实际上能做什么,而不是对象是什么。

希望有所帮助。 如果您不明白,请发表评论,我会尽力向您解释。

维基百科有一个相当详细的解释:

http://en.wikipedia.org/wiki/Duck_typing

鸭子打字是dynamic打字的一种风格,其中一个对象当前的一组方法和属性决定了有效的语义,而不是从一个特定的类或特定的接口实现的inheritance。

重要的注意事项是,用鸭子打字时,开发者更关心被消费的对象的部分,而不是实际的底层types。

考虑你正在devise一个简单的函数,它获得一个Birdtypes的对象并调用它的walk()方法。 有两种方法,你可以想到:

  1. 这是我的function,我必须确定它只接受Bird ,或他们的代码不会编译。 如果有人想用我的function,他一定知道我只接受Bird
  2. 我的函数得到任何objects ,我只是调用对象的walk()方法。 所以,如果object可以walk()它是正确的,如果它不能我的function将失败。 所以在这里重要的是对象是一个Bird或其他任何东西,重要的是它可以walk() (这是鸭子打字

必须考虑到鸭子打字在某些情况下可能是有用的,例如Python使用鸭子打字很多。


有用的阅读

我知道我没有给出广义的答案。 在Ruby中,我们没有声明variables或方法的types – 一切都只是某种对象。 所以规则是“类不是types”

在Ruby中,类永远不会(好,几乎不会)types。 相反,对象的types更多地由该对象可以执行的内容定义。 在Ruby中,我们称之为鸭子打字。 如果一个物体像鸭子一样走路,像鸭子一样说话,那么口译人员很乐意把它当作鸭子来对待。

例如,您可能正在编写一个例程以将歌曲信息添加到string。 如果你来自C#或Java的背景,你可能会写这个:

 def append_song(result, song) # test we're given the right parameters unless result.kind_of?(String) fail TypeError.new("String expected") end unless song.kind_of?(Song) fail TypeError.new("Song expected") end result << song.title << " (" << song.artist << ")" end result = "" append_song(result, song) # => "I Got Rhythm (Gene Kelly)" 

接受Ruby的鸭子打字,你会写一些更简单的东西:

 def append_song(result, song) result << song.title << " (" << song.artist << ")" end result = "" append_song(result, song) # => "I Got Rhythm (Gene Kelly)" 

你不需要检查参数的types。 如果他们支持“(在结果的情况下)或标题和艺术家(在歌曲的情况下),一切都会正常工作。 如果他们不这样做,那么你的方法就会抛出一个exception(就像你检查过的types一样)。 但没有检查,你的方法突然变得更加灵活。 你可以传递一个数组,一个string,一个文件或任何其他使用<<追加的对象,它就可以工作。

鸭子打字:

如果它像鸭子一样说话和走路,那么它就是一只鸭子

这通常被称为绑架诱拐推理或也被称为重新引入 ,我认为更清晰的定义):

  • C (结论, 我们所看到的 )和R (规则, 我们所知道的 ),我们接受/决定/假设P (前提, 财产 )换句话说,一个给定的事实

    医学诊断的基础

    与鸭子: C = 走路,谈话R = 像一只鸭子P = 它是一只鸭子

回到编程:

  • 对象o有方法/属性mp1和接口/typesT需要/定义mp1

  • 对象o有方法/属性mp2和接口/typesT需要/定义mp2

所以,不仅仅是接受任何对象上的mp1 …只要它符合mp1的一些定义…,编译器/运行时也应该可以与断言o是typesT

还有,上面的例子是这样吗? 鸭子打字本质上是不打字的? 或者我们应该把它称为隐式键入?

看语言本身可能有帮助; 它经常帮助我(我不是以英语为母语的人)。

duck typing

1) typing这个词并不意味着在键盘上打字(就像我脑海里的那种持久的意象一样),这意味着要确定“这是什么types的东西?

2) duck这个词expression了这个决定是如何完成的; 这是一种“松散”的决定,如:“ 如果它像鸭子走路…那么它是一只鸭子 ”。 它是“松散的”,因为这个东西可能是鸭子,也可能不是,但它是否真的是鸭子并不重要; 重要的是我可以用鸭子做什么,并期待鸭子performance出的行为。 我可以喂它的面包屑,东西可能会朝我的方向,或者对我充电或退缩…但它不会像灰熊的意志吞噬我。

鸭打字不是types的暗示!

基本上,为了使用“鸭子打字”,你不会针对特定的types,而是针对更广泛的子types(而不是谈论inheritance,当我的意思是指属于同一个configuration文件中的“事物”的子types时) 。

你可以想象一个存储信息的系统。 为了写/读信息,你需要一些存储和信息。

存储的types可能是:文件,数据库,会话等

该界面将让你知道可用的选项(方法),不pipe存储types,这意味着在这一点上没有实现! 换句话说,界面对如何存储信息一无所知。

每个存储系统都必须通过实现相同的方法来了解接口的存在。

 interface StorageInterface { public function write(string $key, array $value): bool; public function read(string $key): array; } class File implements StorageInterface { public function read(string $key): array { //reading from a file } public function write(string $key, array $value): bool { //writing in a file implementation } } class Session implements StorageInterface { public function read(string $key): array { //reading from a session } public function write(string $key, array $value): bool { //writing in a session implementation } } class Storage implements StorageInterface { private $_storage = null; function __construct(StorageInterface $storage) { $this->_storage = $storage; } public function read(string $key): array { return $this->_storage->read($key); } public function write(string $key, array $value): bool { return ($this->_storage->write($key, $value)) ? true : false; } } 

所以现在,每次你需要写/读信息:

 $file = new Storage(new File()); $file->write('filename', ['information'] ); echo $file->read('filename'); $session = new Storage(new Session()); $session->write('filename', ['information'] ); echo $session->read('filename'); 

在这个例子中,你最终在存储构造函数中使用Duck Typing:

 function __construct(StorageInterface $storage) ... 

希望它有帮助;)