【问题标题】:Why don't we require interfaces in dynamic languages?为什么我们不需要动态语言的接口?
【发布时间】:2011-03-05 00:16:23
【问题描述】:

是不是因为动态类型我们在 python 中不需要接口的概念(如 Java 和 C#)?

【问题讨论】:

  • 是的。 (填充剩余空间达到 15 个字符)
  • 我之前问过一个相关的问题。 stackoverflow.com/questions/2350968/…
  • 你怎么知道我们需要什么?
  • 另外,请注意鸭子打字的重要性——我不在乎你是Squid 还是Car——如果你可以.honk(),你是可以接受的。请注意 Python 的整个“让我们模拟一个 file()”模式,这是一个很好的例子来说明为什么接口不存在。

标签: c# java python dynamic-languages


【解决方案1】:

interface 作为关键字和工件是由 Java1 引入的(C# 从那里得到它)来描述对象必须遵守的契约是什么。

但是,接口一直是面向对象范式的关键部分,基本上它代表了对象必须响应的方法。 Java 只是强制执行这种机制来提供静态类型检查。

因此,动态 (OO) 编程语言确实使用接口,即使它们不会静态检查它们。就像其他数据类型一样,例如在 Ruby 中:

 @i = 1;

您不必声明i 类型为FixNum,只需使用它即可。接口也是如此,它们只是流动。权衡是,您不能对此进行静态检查,并且故障仅在运行时显示。

另一方面,Go 或 Scala 等语言使用的 Structural type(或我称之为静态鸭子类型:P)提供了两全其美的效果。

1. 见 Daniel Earwicker 关于 CORBA interface关键字的评论

【讨论】:

  • 如果可以的话,我会添加一个额外的 +1 来提及结构类型。这是一个很棒的概念。
  • 结构类型的另一个 +1
  • “接口作为关键字和工件是由Java引入的”。不太确定。 CORBA 的 IDL (1991) 有 interface 关键字,在 C++ 2.0 (1989) 版中,具有所有纯虚成员函数的类在语义上与接口相同。所以我猜想也许 Java 借用了 CORBA 的关键字,以便特别突出从 C++ 借来的语言特性思想。
  • 静态结构类型的另一个 +1 虽然我不得不再次指出 C++(模板)作为最普遍的例子。
【解决方案2】:

我们不要求他们,但我们确实支持他们。查看Zope Interfaces(可以在 Zope 之外使用)。

【讨论】:

    【解决方案3】:

    值得注意的是,与许多人第一反应会说的相反,接口可以用来做的不仅仅是记录“类支持哪些方法”。 Grzenio 用他关于“实施相同的行为”的措辞触及了这一点。作为这方面的一个具体示例,请查看 Java 接口 Serializable。它没有实现任何方法;相反,它被用作“标记”,表明该类可以安全地序列化。

    以这种方式考虑时,拥有使用接口的动态语言可能是合理的。话虽如此,类似于注解的方法可能是更合理的方法。

    【讨论】:

    • 对于它看起来多么笨拙,这里有一个像 PHP 这样的语言大放异彩的例子。
    【解决方案4】:

    接口在静态类型语言中用于描述两个原本独立的对象“实现相同的行为”。在动态类型语言中,人们隐含地假设当两个对象有一个具有相同名称/参数的方法时,它会做同样的事情,因此接口是没有用的。

    【讨论】:

      【解决方案5】:

      至少一些动态语言使显式接口有点尴尬的一个关键是动态语言通常可以响应他们事先不知道的消息(错误,“方法调用”),甚至做一些事情就像动态创建方法一样。了解对象是否正确响应消息的唯一真正方法是向其发送消息。没关系,因为动态语言认为支持这种事情比静态类型检查更好;一个对象被认为可以在特定协议中使用,因为它“已知”能够参与该协议(例如,通过另一条消息给出)。

      【讨论】:

      • “由另一条消息给出”是指作为参数传递给方法调用,或从方法调用返回。
      【解决方案6】:

      在静态类型语言中使用接口构造来教导类型系统在特定的方法调用上下文中哪些对象可以相互替换。如果两个对象实现了相同的方法,但没有通过从公共基类继承或公共接口的实现相关联,如果您用一个替换另一个,类型系统将在编译时引发错误。

      动态语言使用“duck typing”,这意味着该方法只是在运行时查找,如果它存在且具有正确的签名,则使用它;否则会导致运行时错误。如果两个对象都通过实现相同的方法“像鸭子一样嘎嘎”,那么它们是可替代的。因此,语言没有明确需要通过基类或接口将它们关联起来。

      话虽如此,接口作为一个概念在动态世界中仍然非常重要,但它们通常只是在文档中定义,而不是由语言强制执行。有时,我看到程序员实际上也创建了一个基类来勾勒出用于此目的的接口;这有助于规范化文档,如果接口的一部分可以根据接口的其余部分来实现,则特别有用。

      【讨论】:

        【解决方案7】:

        Perl 有角色(或特征),它不仅仅是接口,不像 java perl 角色,我们可以有一个实现查看这些链接以了解更多关于 perl 角色的信息

        【讨论】:

          【解决方案8】:

          在 C# 和 Java 中,接口只是具有所有抽象方法的抽象类。它们的存在是为了允许伪多重继承,但实际上并不支持成熟的多重继承和多重继承造成的歧义。

          Python 支持multiple inheritance,并且有自己的方式来确定当一个方法存在于多个父级中时应该调用哪个父级的方法。

          【讨论】:

          【解决方案9】:

          动态语言是鸭式的

          如果它像鸭子一样走路和嘎嘎叫 像鸭子,一定是鸭子

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

          换句话说,如果你希望一个对象支持 Delete() 方法,那么你可以只使用

          obj.Delete()
          

          方法,但如果对象不支持 Delete(),则会出现运行时错误。静态类型语言不允许这样做并引发编译时错误。因此,您基本上可以在类型安全性与更快的开发时间和灵活性之间进行权衡。

          没有接口你可以用静态语言做类似的事情:

          void Save(MyBaseClass item)
          {
              if (item.HasChanges)
                  item.Save()
          }
          

          但这将要求您传递给此方法的每个对象都从 MyBaseClass 继承。由于 Java 或 C# 不支持不太灵活的多重继承,因为如果您的类已经继承了另一个类,它也不能从 MyBaseClass 继承。所以更好的选择是创建一个 ISavable 接口并接受它作为输入参数以确保可以保存该项目。那么你就可以兼得:类型安全性和灵活性。

          public interface ISavable
          {
              bool HasChanges {get;set;}
              void Save();
          }
          
          void Save(ISavable item)
          {
              if (item.HasChanges)
                  item.Save()
          }
          

          最后一个后门是使用 object 作为参数,如果你不能期望每个项目都会使用你的 save 方法来实现接口。

          void Save(object item)
          {
              if (item.HasChanges)
                  item.Save()
          }
          

          但同样,您没有编译时检查,如果有人将您的方法与不兼容的类一起使用,您可能会遇到运行时错误。

          【讨论】:

          • “动态语言是 Duck 类型的”。可以这么说是很疯狂的事情。它不必完全正确。
          猜你喜欢
          • 1970-01-01
          • 2011-01-17
          • 2017-08-22
          • 2011-04-01
          • 1970-01-01
          • 2011-12-18
          • 2012-05-23
          • 1970-01-01
          • 2019-06-09
          相关资源
          最近更新 更多