鸭子打字和(java)接口概念
Posted
技术标签:
【中文标题】鸭子打字和(java)接口概念【英文标题】:Duck typing and (java) interface concept 【发布时间】:2011-09-28 14:38:50 【问题描述】:我刚刚阅读了关于 duck typing 的 Wikipedia 文章,我觉得我错过了关于我在 Java 中使用的接口概念的重要一点:
"When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck."
class Duck:
def quack(self):
print("Quaaaaaack!")
def feathers(self):
print("The duck has white and gray feathers.")
def swim(self):
print("Swim seamlessly in the water")
class Person:
def quack(self):
print("The person imitates a duck.")
def feathers(self):
print("The person takes a feather from the ground and shows it.")
def name(self):
print("John Smith")
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
如果我在in_the_forest
中写道:
quack
像鸭子吗?是的
有鸭子feathers
吗?是的
太好了,我们有一只鸭子!
后来,因为我知道它是一只鸭子,所以我想要它到swim
? john
会沉!
我不希望我的应用程序在其过程中崩溃(随机)因为 John 假装是一只鸭子,但我想检查对象的每个属性不是一个明智的主意我收到了...?
【问题讨论】:
总之,你不用担心。您编写了涵盖尽可能多的代码路径和极端情况的良好测试,但 100% 的安全性永远不可能。请注意,Java 或大多数其他静态类型系统也无法获得 100% 的安全性;) +1:一个关于一个重要主题的非常好的问题,有很好的答案。抱歉,我只能给它 1 票。 【参考方案1】:不能说其他语言,但在 python 中它最近(v2.6)引入了Abstract Base Classes (ABC) module。
如果您阅读其介绍背后的基本原理 (PEP 3119),您会很快意识到部分原因是“将约翰从死亡中拯救出来”,或者换句话说,当您编写程序时便于检查事实一个接口,所有的接口方法都会在那里。来自链接的 PEP:
ABC 只是 Python 类 被添加到对象的继承中 树表示某些特征 反对外部检查员。 使用 isinstance() 完成测试,并且 特定 ABC 的存在意味着 测试已通过。此外, ABC 定义了一组最小的 建立方法 类型的特征行为。 区分对象的代码 他们的 ABC 类型可以相信那些 方法将始终存在。
一般来说,您可以为自己的代码应用相同的模式。例如:您可以创建一个 BasePlugin
类,其中包含插件工作所需的所有方法,然后您可以通过子类化它来创建几个不同的插件。根据每个插件必须还是可以定义了这些方法,您可以定义BasePlugin
方法静默传递(插件可以定义这些方法) 或引发异常(插件必须定义这些方法/覆盖BasePlugin
的方法)。
编辑:在下面的 cmets 线程中,有人建议我在答案中包含以下讨论:
这种特性 - 至少在 python 中 - 不是为了人类程序员而实现的(python 从不沉默错误,所以那里已经有很多反馈),而是为了 python 自己的自省能力(从而更容易编写动态加载、元编程代码等...)。换句话说:我知道约翰不会飞……但我希望 python 解释器也知道它! :)
【讨论】:
仅仅因为您可以使用模拟静态类型的Java 主义并不意味着您应该这样做。我还没有看到一个非玩具类实现,其中异构列表的用户还不知道约翰是否会飞。此外,假设您的isinstance
检查失败,无论如何您都必须应对它。这与except
块有何不同? (“继承可能是 OOP 中最诱人的过度使用特性之一”-me)
@msw - 我不懂java,所以平行并不能帮助我理解你的观察(不过,如果你有机会改写的话,我会全神贯注!)。请记住,大多数此类功能 - 至少在 python 中 - 不是为了人类程序员而实现的(python 从不沉默错误,所以那里已经有很多反馈),而是为了 python 自己内省能力(从而使编写动态加载、元编程代码等更容易)。换句话说:我知道约翰不会飞……但我希望 python 解释器也知道它! :)
Python 解释器确实知道这一点。我会在***.com/questions/6576837/… 中重述我的答案,但我觉得我今天是个小教条。我的“Java 主义”可以适用于许多当前流行的 OOP 语言(例如 C++)。我发现dirtsimple.org/2004/12/python-is-not-java.html 有助于打破旧观念。您关于元编程等的观点应该比非元程序员更明显(但 C++ dynamic_casts 也应该如此(如果不使用,为什么会有该功能;))
Ben 的回答更好地说明了我的 cmets。
感谢您的回答,动态类加载是我真正想到的,所以它将帮助我解决我的问题【参考方案2】:
我不希望我的应用程序在其过程中崩溃(随机)因为 John 假装是一只鸭子,但我想检查对象的每个属性不是一个明智的主意我收到了...?
一般来说,这是一个动态类型的问题。在像 Java 这样的静态类型语言中,编译器在编译时检查 Person
是否实现了 IDuck
。在像 Python 这样的动态类型语言中,如果Person
错过了某些特定的鸭子功能(例如swim
),则会出现运行时错误。引用另一篇***文章 ("Type system", Section "Dynamic Typing"):
动态类型可能会导致运行时类型错误——也就是说,在运行时,一个值可能具有意外的类型,并且应用了对该类型无意义的操作。此类错误可能在发生编程错误的地方很久之后才发生——也就是说,错误类型的数据传递到它不应该有的地方。这可能会使错误难以定位。
动态类型有它的缺点(你已经提到过一个)和它的优点。可以在 Wikipedia 中类型系统文章的另一部分中找到简要比较:Static and dynamic type checking in practice。
【讨论】:
【参考方案3】:Duck 打字并不是要检查你需要的东西是否在那里然后使用它们。鸭子打字就是使用你需要的东西。
in_the_forest
函数是由一位考虑鸭子的开发人员编写的。它被设计为在Duck
上运行。 Duck
可以是 quack
和 feathers
,因此编码人员使用这些功能来完成手头的工作。在这种情况下,Duck
也可以 swim
的事实没有被使用,也不需要。
在像 Java 这样的静态语言中,in_the_forest
会被声明为采用 Duck
。当编码人员后来发现他们有一个Person
(也可以是quack
和feathers
)并想重用该函数时,他们运气不好。 Person
是 Duck
的子类吗?不,这似乎一点也不合适。有QuacksAndFeathers
接口吗?也许,如果我们幸运的话。否则我们将不得不做一个,去修改Duck
来实现它,修改in_the_forest
以使用QuacksAndFeathers
而不是Duck
。如果Duck
在外部库中,这可能是不可能的。
在 Python 中,您只需将您的 Person 传递给 in_the_forest
即可。因为事实证明in_the_forest
不需要Duck
,它只需要一个“鸭式”对象,在这种情况下,Person 就足够鸭式了。
game
不过,需要一个不同的“鸭式”定义,它稍微强一些。在这里,John Smith 倒霉了。
现在,Java 确实会在编译时捕捉到这个错误,而 Python 不会。这可以被视为一个劣势。支持动态类型的反对论点是说,您编写的任何实质性代码都将始终包含 no 编译器可以捕获的错误(老实说,Java 甚至不是一个特别好的示例具有强大的静态检查以捕获大量错误的编译器)。所以你需要测试你的代码来找到那些错误。如果您正在测试这些错误,您会很容易发现错误,您将Person
传递给需要Duck
的函数。鉴于此,动态打字员说,一种因为发现一些你的小错误而诱使你不测试的语言实际上是一种坏事物。最重要的是,它会阻止您做一些真正有用的事情,例如在 Person
上重用 in_the_forest
函数。
就我个人而言,我被两个方向撕裂了。我真的很喜欢 Python 的灵活动态类型。我真的很喜欢 Haskell 和 Mercury,因为它们强大的静态类型系统。我不太喜欢 Java 或 C++。在我看来,他们拥有静态类型的所有缺点,而好的部分却很少。
【讨论】:
有些人认为静态类型是一种内置的单元测试系统,但我并没有真正怀念它。就像在现实生活中一样:如果一个人在随机收集的物体中醒来,然后抬头看到一只鸭子坐在他们旁边,那么不久前就出现了严重的问题。 哦,等等,当我希望 Eclipse 的自动完成功能向我展示有用的东西时,我想念它。就是这样。 +1 和免费的肥皂盒:你想用整数填充的语言进行类型安全吗?如果你不想看到一只鸭子坐在你旁边,你需要 Pascal 的范围整数类型(并且没有人想要范围整数(并且最多 2^16 的枚举会很傻))a language that tempts you into not testing ... is a bad thing
我认为你在这一点上是对的,我需要设法把我的想法变成它!
Python 也不会阻止您省略单元测试。如果您不包含单元测试,它会突然停止工作吗?没有。单元测试与否不取决于语言(甚至最流行的单元测试框架之一 jUnit 也是为 Java 编写的)。 The pro-dynamic-typing counter argument is to say that any substantial body of code you write will always contain bugs that no compiler can catch
:总有一天我们会死,并不意味着我们不应该尝试过健康的生活。 IMO,当我们尝试开始重构时,动态语言会让人头疼。【参考方案4】:
动态打字强度,Duck Typing 有另一张卡片,可以实现多个接口,即多个独立的行为特征。
使用 ABC 的正式接口非常棒,可以让您尽早失败。但是,您仍然一次只能实现一个“接口”(您可以从一个父类继承)。
此外,使用类继承,由于其固有的链,您很快就会陷入混乱,并导致紧密耦合和死胡同。由于链上的更改而导致的错误将影响所有可能尚未测试或没有完整测试覆盖率的相关代码。
而使用组合的类反而促进了独立性。 开发人员可以使用必要的接口来规划 UML,以添加多个行为特征(例如 ILogger、IEventGenerator、ICalcArea...),并在代码中使用鸭子类型屏蔽多个接口。当然,缺点是开发人员需要仔细计划以防止名称冲突,并且正如 OP 指出的那样,缺少属性或方法将作为运行时错误而不是编译器警告结束。
回到旧的强/弱交易。
【讨论】:
以上是关于鸭子打字和(java)接口概念的主要内容,如果未能解决你的问题,请参考以下文章