鸭子打字与旧的“变种”类型和/或界面有何不同?

时间:2008-11-14 03:40:59

标签: interface variant duck-typing

我一直看到“鸭子打字”这个短语,甚至还遇到了一两个代码示例。我太忙了 lazy 做我自己的研究,有人可以简单地告诉我:

  • 'duck type'和old-skool'变体类型'之间的区别,以及
  • 提供了一个示例,我可能更喜欢使用变种类型进行鸭子打字,
  • 提供一些我 使用duck typing来完成的事情的例子?

duck typing illustration courtesy of The Register

我并不是想通过怀疑这种“新”结构的力量而感到愤怒,而且我不会因为拒绝做研究而避开这个问题,但是我正在喋喋不休地讨论所有的fl fl最近一直在看。它看起来像是没有打字(也就是动态打字),所以我没有立刻看到它们的优势。

ADDENDUM:感谢目前为止的例子。在我看来,使用类似'O-> can(Blah)'的东西相当于做反射查找(可能不便宜),和/或与编译器(O is IBlah)的编写方式大致相同也许能够检查你,但后者的优点是可以区分我的IBlah界面和你的IBlah界面,而其他两个则没有。当然,每个方法都有很多微小的界面会变得混乱,但是那样可以检查很多单独的方法......

...所以我再也没有得到它。在一个全新的麻袋中,它是一个梦幻般的节省时间,还是同样的老东西? 需要鸭子打字的例子在哪里?

10 个答案:

答案 0 :(得分:32)

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

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

  1. 强类型

    如果语言强制执行程序的类型安全,则强类型语言。这意味着它保证了两件事:称为进步和其他称为保存的东西。进度基本上意味着所有“有效类型”程序实际上可以由计算机运行,它们可能崩溃,或抛出异常,或运行无限循环,但它们实际上可以运行。保存意味着如果程序“有效地键入”,它将始终为“有效键入”,并且没有变量(或内存位置)将包含不符合其指定类型的值。

    大多数语言都具有“进度”属性。然而,有许多不满足“保存”属性。一个很好的例子是C ++(和C)。例如,在C ++中可以强制任何内存地址表现得像任何类型一样。这基本上允许程序员随时违反类型系统。这是一个简单的例子:

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

    此代码允许某人获取一个字符数组并为其编写“foo”实例。如果C ++是强类型的,这是不可能的。类型安全的语言,如C#,Java,VB,lisp,ruby,python和许多其他语言,如果你试图将一个字符数组转换为“foo”实例,就会抛出异常。

  2. 弱打字

    如果没有强类型,则输入的内容很弱。

  3. 静态输入

    如果在编译时验证其类型系统,则静态输入语言。静态类型语言可以像C一样“弱类型”,也可以像C#一样强类型。

  4. 动态输入

    动态类型语言是一种在运行时验证类型的语言。许多语言在静态和动态类型之间都有某种混合。例如,C#将在运行时动态验证多个强制转换,因为在编译时无法检查它们。其他示例是Java,VB和Objective-C等语言。

    还有一些语言是“完全”或“大部分”动态输入的,例如“lisp”,“ruby”和“small talk”

  5. 鸭子打字

    鸭子打字是与静态,动态,弱或强类型完全正交的东西。编写将与对象一起使用的代码的做法,无论其基础类型标识如何。例如,以下VB.NET代码:

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

    无论传递给“Foo”的对象类型是什么,都可以工作,只要它定义了一个名为“Quack”的方法。也就是说,如果物体看起来像鸭子,像鸭子一样走路,像鸭子一样说话,那就是鸭子。鸭子打字有多种形式。可以进行静态鸭子打字,动态鸭子打字,强鸭子打字和周鸭打字。 C ++模板函数是“弱静态鸭子打字”的一个很好的例子。 “JaredPar”帖子中的示例节目显示了“强静态鸭子打字”的示例。 VB中的后期绑定(或Ruby或Python中的代码)可以实现“强大的动态鸭子打字”。

  6. 变体

    变体是动态类型的数据结构,可以包含一系列预定义的数据类型,包括字符串,整数类型,日期和com对象。然后,它定义了一系列操作,用于分配,转换和操作变体中存储的数据。变量是否是强类型取决于它的使用语言。例如,VB 6程序中的变体是强类型的。 VB运行时确保用VB代码编写的操作符合变体的输入规则。绑定通过VB中的变体类型向IUnknown添加字符串将导致运行时错误。但是,在C ++中,变体是弱类型的,因为所有C ++类型都是弱类型的。

  7. 好的....现在我已经完成了定义,我现在可以回答你的问题了:

    在VB 6中,一种变体可以实现一种形式的鸭子打字。有更好的方法做鸭子打字(Jared Par的例子是最好的),而不是变种,但你可以用变种做鸭子打字。也就是说,您可以编写一段代码来操作对象,而不管其基础类型标识。

    但是,使用变体进行此操作并不能真正进行大量验证。一个静态类型的duck类型机制,就像JaredPar描述的那样,给出了duck typing的好处,以及编译器的一些额外验证。这真的很有用。

答案 1 :(得分:18)

简单的答案是变体是弱类型的,而鸭子打字是强类型的。

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

interface IDuck {
  void Quack();
}

现在让我们来看看Daffy

class Daffy {
  void Quack() {
    Console.WriteLine("Thatsssss dispicable!!!!");
  }
}
在这种情况下,Daffy实际上并不是IDuck。然而它就像鸭子一样。为什么让Daffy实施IDuck,因为很明显Daffy实际上是一只鸭子。

这就是Duck输入的地方。它允许在任何具有IDuck和IDuck引用的所有行为的类型之间进行类型安全转换。

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

Quack方法现在可以在“d”上调用,具有完整的类型安全性。此赋值或方法调用中不存在运行时类型错误的可能性。

答案 2 :(得分:5)

鸭子打字只是动态打字或后期绑定的另一个术语。在运行时可能或不实际定义的任何成员访问(例如,obj.Anything)解析/编译的变体对象是duck typing。

答案 3 :(得分:4)

可能没有需要鸭子打字,但在某些情况下它可能很方便。 假设你有一个方法,从一些第三方库获取并使用密封类Duck的对象。并且您希望使该方法可测试。而鸭子有一个非常大的API(有点像ServletRequest),你只需要关心一小部分。你是如何测试它的?

一种方法是让方法采取嘎嘎叫的方法。然后你可以简单地创建一个quacking模拟对象。

答案 4 :(得分:3)

尝试阅读关于鸭子打字的维基百科文章的第一段 Duck typing on Wikipedia

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

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

“如果它像鸭子一样Run()”

变体更像是.NET中的对象。

答案 5 :(得分:2)

@Kent Fredric

你的例子肯定可以通过使用显式接口而不用鸭子打字来完成......更好的是,但这并非不可能。

就个人而言,我发现界面中明确定义的合同对于执行质量代码要好得多,而不是依赖于鸭子打字......但这只是我的意见并且带着一点点盐。

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

答案 6 :(得分:2)

关于你的需要使用鸭子打字来完成某事的例子的请求,我不认为存在这样的事情。我认为这就像我考虑是否使用递归或是否使用迭代。有时一个比另一个好。

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

我说如果你习惯使用静态类型,那么就使用它。但你应该至少尝试打字(如果可能的话,在一个非常重要的项目中使用它)。

更直接地回答你:

  

...所以我再也没有得到它。在一个全新的麻袋中,它是一个梦幻般的节省时间,还是同样的老东西?

两者都是。你还在攻击同样的问题。你只是以不同的方式做到这一点。有时候,你真的需要做的就是节省时间(即使没有其他理由强迫自己考虑以不同的方式做事)。

它是否能够拯救全人类免于灭绝的灵丹妙药?没有。任何告诉你的人都是狂热者。

答案 7 :(得分:1)

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

在OO语言中它可能更实用,其中基元不是基元,而是对象。

我认为最好的总结方式,在变体类型中,实体是/可以是任何东西,它是什么是不确定的,而不是只有看起来的实体,但是你可以通过询问来弄清楚它是什么。

如果没有躲避,我认为这是不可信的。

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 ); 
    }); 
 }

任何其他方式都需要你对函数设置类型限制,这会削减不同的物种,需要你为不同的物种创建不同的函数,使得代码真的很难维护。

这真的很糟糕,只是想要进行良好的舞蹈编排。

答案 8 :(得分:1)

一个变体(至少我在VB6中使用它们)拥有一个定义良好,通常是静态类型的变量。例如,它可能包含int,float或字符串,但变量int用作int,变量float用作浮点数,变量字符串用作字符串。

鸭子打字改为使用动态打字。在duck typing下,如果变量支持int或float或string在特定上下文中支持的特定方法,则变量可以用作int,float或字符串。

变种与鸭子打字的例子:

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

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

使用duck typing:我可以使用我的UserFromLdap类并添加一些使其像UserFromDbRecord类一样的方法。我不需要复制整个FromDbRecord接口,只需要我需要使用的例程。如果我这样做,它是一种非常强大和灵活的技术。如果我做错了,它会产生非常令人困惑和脆弱的代码(如果DB库或LDAP库发生更改,则会出现破坏)。

答案 9 :(得分:0)

你可以用鸭子打字做的一切,你也可以使用接口。鸭子打字是快速和舒适的,但有些人认为它可能导致错误(如果两个不同的方法/属性被命名相似)。接口是安全和明确的,但人们可能会说“为什么说明显?”。休息是一种火焰。每个人都选择适合他的东西,没有人是“正确的”。