关于php:动态语言的界面是否有意义?

关于php:动态语言的界面是否有意义?

Is there any point for interfaces in dynamic languages?

在Java等静态语言中,您需要接口,因为
否则类型系统将不允许您做某些事情。
但是在动态语言(例如PHP和Python)中,
鸭式打字的优势。

PHP支持接口。
Ruby和Python没有它们。
因此,没有他们,您显然可以快乐地生活。

我主要是在PHP中工作,从未真正
利用定义接口的能力。 当我需要一个
一组实现某些通用接口的类,然后
我只是在文档中描述它。

所以你怎么看? 不使用就好了吗
界面完全不是动态语言吗?


我认为这更多是为了方便。如果您的函数需要一个"类似于文件的"对象,并且仅在其上调用read()方法,那么强制用户实现某种File接口是不便的,甚至是限制性的。检查对象是否具有read方法同样容易。

但是,如果您的函数需要大量方法,则检查对象是否支持接口然后检查每个方法的支持会更容易。


是的,有一点

如果您未明确使用接口,则您的代码仍将使用该对象,就好像它实现了某些方法一样,只是不清楚什么是未公开的接口。

如果您定义一个函数来接受一个接口(用PHP表示),那么它会更早地失败,并且问题将出在调用者而不是执行工作的方法上。通常,较早失败是遵循的良好经验法则。


实际上,接口向具有Java的静态语言增加了某种程度的动态lang-like灵活性。它们提供了一种查询对象的方式,该对象在运行时为其实现了契约。

这个概念很好地移植到了动态语言中。当然,根据您对"动态"一词的定义,甚至包括Objective-C,它在Cocoa中相当广泛地使用了协议。

在Ruby中,您可以询问对象是否响应给定的方法名称。但这是一个很薄弱的保证,它无法执行您想要的操作,特别是考虑到一遍又一遍地使用了很少的单词,没有考虑完整的方法签名等。

在Ruby中,我可能会问

1
object.respond_to? :sync

所以,无论如何,它都有一个名为" sync"的方法。

在Objective-C中,我可能会问类似的问题,即"这种外观/行走/嘎嘎声是否像同步的东西?":

1
[myObject respondsToSelector:@selector(sync)]

更好的是,以一些冗长的代价为代价,我可以问一个更具体的问题,即"此外观/行走/声音像同步到MobileMe的东西吗?":

1
[myObject respondsToSelector:@selector(sync:withMobileMeAccount:)]

那只鸭子低到物种的水平。

但是要真正问一个对象是否有希望实现与MobileMe的同步...

1
[receiver conformsToProtocol:@protocol(MobileMeSynchronization)]

当然,您可以通过仅检查是否存在一系列选择器(它们考虑了协议/鸭子的定义)以及它们是否足够具体来实现协议。在什么时候协议只是一大堆丑陋的responses_to的缩写?查询和一些非常有用的语法糖供编译器/ IDE使用。

接口/协议是对象元数据的另一个维度,可用于在处理这些对象时实现动态行为。在Java中,编译器恰好需要对常规方法调用进行此类操作。但是,即使动态语言(如Ruby,Python,Perl等)也实现了类型概念,而不仅仅是"对象响应什么方法"。因此是class关键字。没有这种概念,JavaScript是唯一真正真正使用的语言。如果您有类,那么接口也很有意义。

与大多数应用程序代码相比,对于更复杂的库或类层次结构而言,它无疑更有用,但是我认为该概念在任何语言中都非常有用。

另外,其他人也提到了mixins。 Ruby mixins是共享代码的一种方式-例如,它们与类的实现有关。接口/协议是关于类或对象的接口的。它们实际上可以彼此互补。您可能具有指定行为的接口,以及一个或多个帮助对象实现该行为的混入。

当然,我想不出真正具有两种独特的一流语言功能的任何语言。在具有mixin的组件中,包括mixin通常意味着它实现的接口。


如果您没有很高的安全性约束(因此没有人会以您不想使用的方式访问您的数据),并且您拥有良好的文档或训练有素的编码人员(因此他们不需要解释器/编译器来告诉他们如何处理)做),那么不,这是没有用的。

对于大多数中等规模的项目,只需要键入鸭子即可。


我给人的印象是Python没有接口。据我所知,在Python中您不能完全因为编译器是动态语言而强制执行要在编译时实现的方法。

有适用于Python的接口库,但我从未使用过它们。

Python还具有Mixins,因此您可以通过为每个方法实现定义一个pass的Mixin来创建一个Interface类,但这并没有真正给您带来太多价值。


我认为接口的使用更多取决于要使用您的库的人数。如果只是您或一个小团队,那么文档和约定将很好,并且需要接口将成为障碍。如果它是一个公共库,那么接口会更加有用,因为它们会约束人们提供正确的方法,而不仅仅是提示。因此,接口绝对是编写公共库的宝贵功能,我认为缺少(或至少不加强调)是将动态语言更多地用于应用程序而将强类型语言用于大型库的众多原因之一。


在PHP之类的语言中,不存在的方法调用会导致致命错误并导致整个应用程序崩溃,因此,接口是有意义的。

在像Python这样的语言中,您可以捕获和处理无效的方法调用,而事实并非如此。


Rene,请在StackOverflow上阅读我对"以动态语言构建大型系统的最佳实践"问题的答案。我讨论了放弃动态语言自由的一些好处,以节省开发工作并简化向该项目介绍新程序员的工作。接口,如果使用得当,将大大有助于编写可靠的软件。


Python 3000将具有抽象基类。非常值得一读。


Java"接口"的一种用法是允许Java中使用强类型的混合。您可以混合使用适当的超类以及为支持接口而实现的所有其他方法。

Python具有多重继承,因此它实际上不需要接口替代来允许来自多个超类的方法。

但是,我喜欢强类型化的一些好处-主要是,我喜欢早期错误检测。我尝试使用"类似接口"的抽象超类定义。

1
2
3
4
5
6
7
8
class InterfaceLikeThing( object ):
    def __init__( self, arg ):
        self.attr= None
        self.otherAttr= arg
    def aMethod( self ):
        raise NotImplementedError
    def anotherMethod( self ):
        return NotImplemented

这以某种方式使接口正式化。它不能为符合期望的子类提供绝对的证据。但是,如果子类无法实现所需的方法,则我的单元测试将失败,并返回明显的NotImplemented返回值或NotImplementedError异常。


作为PHP程序员,按照我的看法,Interface基本上用作合同。它可以让您说使用此接口的所有内容都必须实现一组给定的功能。

我不知道这是否有用,但是在尝试了解接口的全部含义时,我发现这是一个绊脚石。


除了其他答案外,我只想指出Javascript具有instanceof关键字,如果给定实例在给定对象的原型链中的任何位置,该关键字都会返回true。

这意味着,如果在原型链中将"接口对象"用于"实现对象"(都是JS的普通对象),则可以使用instanceof来确定它是否"实现"了它。这对执行方面没有帮助,但是在多态方面却有帮助-这是接口的一种常见用法。

MDN实例参考


好吧,首先,是对的,Ruby本身没有接口,但是它们具有混合功能,这在某种程度上兼顾了接口和其他语言的抽象类的优点。

接口的主要目标是确保您的对象应实现接口本身中存在的所有方法。

当然,接口从来都不是强制性的,即使在Java中,您也可以想象仅在不知道要处理的对象类型时,仅使用类并使用反射来调用方法,但是这种方法容易出错,因此不建议使用很多种方法。


就像说您不需要动态类型语言中的显式类型。为什么不将所有内容都设为" var"并在其他位置记录其类型?

这是程序员对程序员的限制。这使您很难用脚射击。给您更少的出错空间。


好吧,检查给定对象是否支持整个接口肯定会更容易,而不仅仅是在调用初始方法中使用的一个或两个方法(例如将一个对象添加到内部列表)时不会崩溃。

鸭子输入具有接口的一些优点,即在任何地方都易于使用,但是检测机制仍然缺失。


如果需要,可以实现一种接口,该接口的功能可以将对象的方法/属性与给定签名进行比较。这是一个非常基本的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
file_interface = ('read', 'readline', 'seek')

class InterfaceException(Exception): pass

def implements_interface(obj, interface):
    d = dir(obj)
    for item in interface:
        if item not in d: raise InterfaceException("%s not implemented." % item)
    return True

>>> import StringIO
>>> s = StringIO.StringIO()
>>> implements_interface(s, file_interface)
True
>>>
>>> fp = open('/tmp/123456.temp', 'a')    
>>> implements_interface(fp, file_interface)
True
>>> fp.close()
>>>
>>> d = {}
>>> implements_interface(d, file_interface)
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 4, in implements_interface
__main__.InterfaceException: read not implemented.

当然,这并不能保证。


停止尝试以动态语言编写Java。


推荐阅读