Python中的Java抽象/接口设计

2022-08-31 22:27:25

我有许多类,它们都共享相同的方法,只是具有不同的实现。在Java中,让这些类中的每一个实现一个接口或扩展一个抽象类是有意义的。Python是否有类似的内容,或者我应该采取其他方法?


答案 1

Python中的接口背后有一些故事。最初的态度,多年来一直占主导地位,是你不需要它们:Python的工作原理是EAFP(请求宽恕比许可更容易)原则。也就是说,不是指定你接受一个我不知道的ICloseable对象,你只是在需要时尝试该对象,如果它引发异常,那么它就会引发异常。close

因此,在这种心态下,你只需单独编写你的类,并按照自己的意愿使用它们。如果其中一个不符合要求,您的程序将引发异常;相反,如果您使用正确的方法编写另一个类,那么它将正常工作,而无需指定它实现您的特定接口。

这工作得很好,但是接口有明确的用例,特别是对于较大的软件项目。Python中的最终决定是提供abc模块,它允许您编写抽象基类,即除非您重写其所有方法,否则无法实例化的类。这是你的决定,你是否认为使用它们是值得的。

PEP引入ABC的解释比我解释得更好:

在面向对象编程领域,与对象交互的使用模式可以分为两个基本类别,即“调用”和“检查”。

调用意味着通过调用对象的方法与对象进行交互。通常,这与多态性相结合,因此调用给定的方法可能会根据对象的类型运行不同的代码。

检查是指外部代码(对象方法之外)能够检查该对象的类型或属性,并根据该信息决定如何处理该对象。

这两种使用模式具有相同的一般目的,即能够以统一的方式支持处理各种可能新颖的对象,但同时允许针对每种不同类型的对象自定义处理决策。

在经典的OOP理论中,调用是首选的使用模式,并且主动不鼓励检查,这被认为是早期过程编程风格的遗物。然而,在实践中,这种观点过于教条和不灵活,并导致一种设计僵化,这与Python等语言的动态性质非常不一致。

特别是,通常需要以对象类的创建者未预料到的方式处理对象。构建满足该对象的每个可能用户的需求的每个对象方法并不总是最佳解决方案。此外,有许多强大的调度哲学与经典的OOP要求形成鲜明对比,即行为被严格封装在对象中,例如规则或模式匹配驱动的逻辑。

另一方面,经典OOP理论家对检查的批评之一是缺乏形式主义和正在检查的内容的临时性。在像Python这样的语言中,对象的几乎任何方面都可以被外部代码反映和直接访问,有许多不同的方法来测试对象是否符合特定的协议。例如,如果询问“此对象是可变序列容器吗?”,则可以查找“list”的基类,也可以查找名为“_getitem_”的方法。但请注意,尽管这些测试可能看起来很明显,但它们都不正确,因为一个会产生假阴性,而另一个会产生假阳性。

普遍同意的补救措施是将测试标准化,并将它们分组到正式的安排中。这最容易通过继承机制或其他方式将一组标准的可测试属性与每个类相关联来实现。每个测试都附带一组承诺:它包含有关类的一般行为的承诺,以及关于其他类方法将可用的承诺。

此 PEP 提出了一种用于组织这些测试的特定策略,称为抽象基类或 ABC。ABC 只是一些 Python 类,它们被添加到对象的继承树中,以向外部检查器发出该对象的某些特征信号。测试是使用 isinstance() 完成的,并且存在特定的 ABC 意味着测试已通过。

此外,ABC 还定义了一组最小的方法来建立类型的特征行为。根据对象的 ABC 类型区分对象的代码可以相信这些方法将始终存在。这些方法中的每一个都伴随着一个广义的抽象语义定义,该定义在ABC的文档中进行了描述。这些标准语义定义不是强制执行的,但强烈建议使用。

像Python中的所有其他内容一样,这些承诺具有绅士协议的性质,在这种情况下,这意味着虽然该语言确实执行了ABC中所做的一些承诺,但具体类的实现者必须确保其余的承诺得到保留。


答案 2

我对Python并不熟悉,但我冒昧地猜测它没有。

Java 中存在接口的原因是它们指定了一个协定。例如,实现的东西保证有一个方法符合接口上定义的一般行为。您可以在不知道List特定类的情况下插入List的任何(合理的)实现,调用接口上定义的方法序列并获得相同的一般行为。java.util.Listadd()

此外,开发人员和编译器都可以知道这样的方法存在并且可以在所讨论的对象上调用,即使他们不知道其确切的类。这是一种多态性形式,静态类型需要它来允许不同的实现类,但仍然知道它们都是合法的。

这在Python中并不真正有意义,因为它不是静态类型的。您不需要声明对象的类,也不需要说服编译器您调用它的方法肯定存在。鸭子类型世界中的“接口”就像调用方法并相信对象可以适当地处理该消息一样简单。

注意 - 欢迎来自知识渊博的Pythonista的编辑。