鸭子的input与旧的“变种”types和/或接口有什么不同?

我一直看到“鸭子打字”这个词汇泛滥,甚至横跨了一两个代码。 我太懒了,忙着做我自己的研究,有人能告诉我,简单地说:

  • “鸭型”和旧型“变型”之间的区别,以及
  • 提供了一个例子,我可能更喜欢鸭子打字变种,和
  • 提供一个例子,我将不得不使用鸭子打字完成?

登录的鸭子打字的例证礼貌

我不是故意怀疑这个“新”构造的力量,我并不是在拒绝这个研究,而是回避这个问题,但是我正在扼杀我所看到的所有植绒炒作最近。 它看起来像没有打字(又名dynamic打字)给我,所以我没有看到马上的优势。

附录:感谢迄今的例子。 在我看来,使用像'O-> can(Blah)'这样的东西就等同于做一个reflection查找(这可能不便宜),和/或大概就像编译器可能会说的(O是IBlah)一样能够检查你,但后者有区分我的IBlah接口和你的IBlah接口的优势,而另外两个则没有。 当然,每个方法都有很多小的接口会变得混乱,但是接下来可以检查很多单独的方法。

…所以我再也没有得到它。 这是一个很棒的节省时间,还是一个全新的袋子里的老东西? 这个例子需要打字吗?

简单的答案是弱types,而鸭子打字是强types的。

鸭子打字可以很好地概括为“如果它像鸭子一样走路,看起来像一只鸭子,就像一只鸭子,那么它就是一只鸭子。” 它的计算机科学术语认为鸭子是以下界面。

interface IDuck { void Quack(); } 

现在让我们来看看Daffy

 class Daffy { void Quack() { Console.WriteLine("Thatsssss dispicable!!!!"); } } 

达菲在这种情况下实际上并不是一个IDuck。 然而,它就像一只鸭子。 为什么Daffy实施Iduck的时候很显然Daffy实际上是一只鸭子。

这是Duck打字进来的地方。它允许在具有IDuck和IDuck引用的所有行为的任何types之间的types安全转换。

 IDuck d = new Daffy(); d.Quack(); 

现在可以在完全types安全的“d”上调用Quack方法。 在这个赋值或方法调用中没有发生运行时types错误的机会。

在这里的一些答案中,我看到一些术语的错误使用,导致人们提供错误的答案。

所以,在我给出答案之前,我将提供几个定义:

  1. 强types

    强制执行程序types安全的语言是强types的。 这意味着它保证了两件事情:称为进步的东西以及其他称为保存的东西。 进度基本上意味着所有“有效types”的程序实际上可以被计算机运行,它们可能会崩溃,或者抛出一个exception,或者运行一个无限循环,但是它们实际上可以运行。 保存意味着如果一个程序是“有效input的”,它将总是“有效的input”,并且没有variables(或存储单元)将包含一个不符合其指定types的值。

    大多数语言都有“进度”属性。 然而,有许多不符合“保存”的财产。 一个很好的例子是C ++(也是C)。 例如,C ++可以强制任何内存地址的行为,就像它是任何types一样。 这基本上允许程序员随时违背types系统。 这是一个简单的例子:

     struct foo { int x; iny y; int z; } char * x = new char[100]; foo * pFoo = (foo *)x; foo aRealFoo; *pFoo = aRealFoo; 

    这段代码允许某人获取一组字符,并为其写入一个“foo”实例。 如果C ++是强types的,这是不可能的。 types安全的语言,比如C#,Java,VB,lisp,ruby,python等等,如果你试图将一个字符数组转换成“foo”实例,会引发exception。

  2. 弱types

    如果input的内容不是强types的,则input的内容很弱。

  3. 静态types

    如果在编译时validationtypes系统,则语言是静态types的。 静态types的语言可以像C一样“弱types化”,也可以像C#一样强types化。

  4. dynamicinput

    dynamictypes语言是在运行时validationtypes的语言。 许多语言在静态和dynamicinput之间有某种混合。 例如,C#会在运行时dynamicvalidation多个强制转换,因为在编译时无法检查它们。 其他的例子是Java,VB和Objective-C等语言。

    也有一些“完全”或“大部分”dynamictypes的语言,如“lisp”,“ruby”和“小谈话”

  5. 鸭子打字

    鸭子打字是完全正交的静态,dynamic,弱,或强打字。 编写代码的做法是与对象一起工作,而不pipe其基础types标识如何。 例如,下面的VB.NET代码:

     function Foo(x as object) as object return x.Quack() end function 

    无论传入“Foo”的对象的types如何,都可以工作,只要定义了一个叫做“Quack”的方法即可。 也就是说,如果物体看起来像一只鸭子,像鸭子一样走路,像鸭子一样说话,那么它就是一只鸭子。 鸭子打字有很多种forms。 有可能有静态鸭子打字,dynamic鸭子打字,强鸭子打字和周鸭子打字。 C ++模板函数是“弱静态鸭子打字”的一个很好的例子。 在“JaredPar的”文章中显示的例子显示了“强静态鸭子打字”的例子。 VB中的后期绑定(或Ruby或Python中的代码)使得“强大的dynamic鸭子打字”成为可能。

  6. 变种

    变体是一个dynamictypes的数据结构,可以保存一系列预定义的数据types,包括string,整数types,date和com对象。 然后定义了一组操作来分配,转换和操作variables中存储的数据。 强types的变体是否取决于所使用的语言。 例如,VB 6程序中的变体是强types的。 VB运行时确保用VB代码编写的操作符合变体的input规则。 在VB中通过变体types添加一个string到IUnknown将导致运行时错误。 然而,在C ++中,变体是弱types的,因为所有的C ++types都是弱types的。

好的….现在我已经得到了定义,我现在可以回答你的问题:

在VB 6中的一个变体,使一种forms的鸭子打字。 有更好的方法来做鸭子打字(Jared Par的例子是最好的之一),比变种,但你可以做鸭子打字变种。 也就是说,您可以编写一段代码,而不pipe其基础types标识如何。

但是,使用变体来做并不能真正提供很多validation。 一个静态types的鸭式机制,就像JaredPar描述的那样,给出了鸭式打印的好处,以及来自编译器的一些额外的validation。 这可能是非常有帮助的。

鸭子打字只是dynamic打字或迟绑定的另一个术语。 在运行时可能实际定义或不实际定义的任何成员访问(例如,obj.Anything)进行parsing/编译的变体对象是鸭子打字。

也许没有什么需要鸭子打字,但在某些情况下可以很方便。 假设你有一个方法从一些第三方库中获取并使用密封类Duck的对象。 你想让这个方法可以testing。 而鸭子有一个非常大的API(有点像ServletRequest),你只需要关心一个小子集。 你如何testing它?

一种方法是使方法采取一些嘎嘎的东西。 那么你可以简单地创build一个quacking模拟对象。

尝试阅读维基百科文章中有关鸭子打字的第一段。
鸭维基百科打字

我可以有一个定义Run()方法的接口(IRunnable)。
如果我有另一个类的方法是这样的:
public void RunSomeRunnable(IRunnable rn){…}

在鸭子友好的语言中,我可以将任何具有Run()方法的类传入RunSomeRunnable()方法。
在静态types语言中,传递给RunSomeRunnable的类需要显式实现IRunnable接口。

“如果它运行()像一只鸭子”

至less在.NET中变体更像是对象。

@肯特弗雷德里克

你的例子大多数情况下可以通过使用明确的接口来完成,而不需要使用鸭子键入… uglier是的,但这不是不可能的。

就个人而言,我发现在界面上定义良好的合同对于执行质量代码要好得多,而不是依靠鸭子打字……但是这只是我的看法,并且带着一丝盐。

 public interface ICreature { } public interface IFly { fly();} public interface IWalk { walk(); } public interface IQuack { quack(); } // ETC // Animal Class public class Duck : ICreature, IWalk, IFly, IQuack { fly() {}; walk() {}; quack() {}; } public class Rhino: ICreature, IWalk { walk(); } // In the method List<ICreature> creatures = new List<ICreature>(); creatures.Add(new Duck()); creatures.Add(new Rhino()); foreach (ICreature creature in creatures) { if (creature is IFly) (creature as IFly).fly(); if (creature is IWalk) (creature as IWalk).walk(); } // Etc 

关于你要求用鸭子打字完成的事情的例子,我不认为有这样的事情存在。 我认为这是想我是否使用recursion或是否使用迭代。 有时一个比另一个更好。

根据我的经验,鸭子打字使得代码更易读,更容易掌握(对程序员和读者而言)。 但是我发现更传统的静态input消除了很多不必要的input错误。 没有办法客观地说一个人比另一个人好,甚至不能说一个人比另一个人更有效。

我说,如果你使用静态types,然后使用它。 但是你至less应该尝试打字(如果可能的话,在一个非平凡的项目中使用它)。

为了更直接地回答你:

…所以我再也没有得到它。 这是一个很棒的节省时间,还是一个全新的袋子里的老东西?

这是两个。 你仍然在攻击同样的问题。 你只是在做一个不同的方式。 有时候,这就是为了节省时间而必须做的所有事情(即使没有其他理由强迫自己去考虑以不同的方式做事)。

是拯救全人类灭绝的万灵丹吗? 不,任何告诉你的人都是狂热者。

一个变体(至less就像我在VB6中使用过的那样)拥有一个单一的,明确定义的,通常是静态types的variables。 例如,它可能包含一个int,一个浮点数或一个string,但是variablesint用作整数,variables浮点数用作浮点数,variablesstring用作string。

鸭子打字,而是使用dynamic打字。 在鸭子打字的情况下,如果一个variables碰巧支持int,float或者string在特定的上下文中支持的特殊方法,那么这个variables可以用作int,float或者string。

变体与鸭子打字的例子:

对于Web应用程序,假设我希望我的用户信息来自LDAP而不是来自数据库,但是我仍然希望我的用户信息可以由基于数据库和ORM的其他Web框架使用。

使用变体:没有运气。 我可以创build一个可以包含UserFromDbRecord对象或UserFromLdap对象的变体,但UserFromLdap对象将不能使用来自FromDbRecord层次结构的对象的例程。

使用鸭子打字:我可以把我的UserFromLdap类,并添加一些使它像一个UserFromDbRecord类的行为的方法。 我不需要复制整个FromDbRecord接口,只需要使用我需要的例程。 如果我这样做,这是一个非常强大和灵活的技术。 如果我做错了,它会产生非常混乱和脆弱的代码(如果数据库库或LDAP库发生更改,可能会发生破坏)。

我认为鸭子打字的核心是如何使用它。 一个人使用方法检测和实体的内省,以知道如何处理它,而不是事先声明它将是什么(你知道如何处理它)。

在OO语言中,这可能更实用,其中原语不是原语,而是对象。

我认为总结它的最好的方式,在变体types,一个实体是/可以是任何东西,它是不确定的,而不是一个实体只看起来像什么,但你可以通过问它。

这是我不相信是没有鸭型的似是而非。

 sub dance { my $creature = shift; if( $creature->can("walk") ){ $creature->walk("left",1); $creature->walk("right",1); $creature->walk("forward",1); $creature->walk("back",1); } if( $creature->can("fly") ){ $creature->fly("up"); $creature->fly("right",1); $creature->fly("forward",1); $creature->fly("left", 1 ); $creature->fly("back", 1 ); $creature->fly("down"); } else if ( $creature->can("walk") ) { $creature->walk("left",1); $creature->walk("right",1); $creature->walk("forward",1); $creature->walk("back",1); } else if ( $creature->can("splash") ) { $creature->splash( "up" ) for ( 0 .. 4 ); } if( $creature->can("quack") ) { $creature->quack(); } } my @x = (); push @x, new Rhinoceros ; push @x, new Flamingo; push @x, new Hyena; push @x, new Dolphin; push @x, new Duck; for my $creature (@x){ new Thread(sub{ dance( $creature ); }); } 

任何其他的方式都会要求你对函数进行types限制,这会限制不同的物种,需要你为不同的物种创build不同的函数,使得代码真的很难维护。

这真的很糟糕,只是试图执行好的舞蹈编排。

所有你可以用鸭子打字你也可以做接口。 鸭子打字是快速和舒适的,但有些人认为它可能会导致错误(如果两个不同的方法/属性是相同的)。 接口是安全和明确的,但人们可能会说“为什么陈述明显?”。 rest是一种火焰。 每个人都select适合自己的东西,没有人是“正确的”。

Interesting Posts