【问题标题】:How do I argue against Duck-typing in a strongly typed language like Java?我如何反对像 Java 这样的强类型语言中的 Duck 类型?
【发布时间】:2009-03-31 05:21:38
【问题描述】:

我在一个 Java 程序员团队工作。我的一位同事不时建议我做一些类似“只需添加一个类型字段”(通常是“字符串类型”)的事情。或者代码将被提交满载“if (foo instanceof Foo){...} else if( foo instanceof Bar){...}”。

尽管 Josh Bloch 警告说“标记类是对适当类层次结构的模仿”,但我对这类事情的单线反应是什么?那我该如何更认真地阐述这个概念呢?

我很清楚 - 上下文是 Java - 所考虑的对象类型就在我们集体面前 - IOW:紧跟在“类”、“枚举”或“接口”等之后的词。

但是除了难以演示或量化(当场)“它使您的代码更复杂”之外,我怎么说“在(或多或少)强类型语言中进行鸭子打字是一种愚蠢的想法暗示了更深层次的设计病态?

【问题讨论】:

  • 这不是鸭子打字。鸭子类型只是动态语言(隐式接口)中的多态性。你所说的只是类型检查(即使在鸭子类型的语言中也很糟糕,比如 Python)。
  • @Devin Jeanpierre,@sharptooth,既然我们知道这不是鸭式打字,难道不应该有人修复问题的标题吗?

标签: java duck-typing


【解决方案1】:

其实你说的还不错。

事实是,comb 的“实例”几乎总是一个坏主意(例如,当您正在编组或序列化时会发生异常,当您在短时间内可能没有所有类型信息时。)正如 josh 所说,否则这是一个糟糕的类层次结构的标志。

知道这是一个坏主意的方式是它会使代码变得脆弱:如果您使用它,并且类型层次结构更改,那么它可能会破坏它梳子的实例出现在任何地方。更重要的是,你会失去强类型的好处;编译器无法通过提前捕获错误来帮助您。 (这有点类似于 C 中类型转换引起的问题。)

更新

让我稍微扩展一下,因为从评论看来我不太清楚。您在 C 中使用类型转换或 instanceof 的原因是您想说“好像”:使用此 foo 就好像它是 bar。现在,在 C 中,根本没有运行时类型信息,所以您只是在没有网络的情况下工作:如果您对某些内容进行类型转换,生成的代码将把该地址视为包含特定类型,无论如何,并且您应该只希望它会导致运行时错误,而不是默默地破坏某些东西。

Duck 打字只是把它提升到一个规范;在像 Ruby、Python 或 Smalltalk 这样的动态弱类型语言中,一切都是无类型引用;你在运行时向它发送消息,看看会发生什么。如果它理解特定的信息,它就会“像鸭子一样走路”——它会处理它。

这非常方便和有用,因为它允许奇妙的 hack,例如将生成器表达式分配给 Python 中的变量,或将块分配给 Smalltalk 中的变量。但这确实意味着您在运行时容易受到强类型语言在编译时可以捕获的错误的影响。

在像 Java 这样的强类型语言中,您根本不能真正严格地进行鸭式类型:您必须告诉编译器您将把某物视为什么类型。你可以通过使用类型转换来获得类似鸭子类型的东西,这样你就可以做类似的事情

Object x;   // A reference to an Object, analogous to a void * in C

// Some code that assigns something to x

((FoodDispenser)x).dropPellet();          // [1]

// Some more code

((MissleController)x).launchAt("Moon");   // [2]

现在在运行时,只要 x 是 [1] 处的 FoodDispenser 或 [2] 处的 MissleController 就可以了;否则繁荣。或者出乎意料的是,没有繁荣

在您的描述中,您使用else ifinstanceof 的组合来保护自己

 Object x ;

 // code code code 

 if(x instanceof FoodDispenser)
      ((FoodDispenser)x).dropPellet();
 else if (x instanceof MissleController )
      ((MissleController)x).launchAt("Moon");
 else if ( /* something else...*/ ) // ...
 else  // error

现在,您可以免受运行时错误的影响,但您有责任稍后在else 做一些明智的事情。

但是现在假设您对代码进行了更改,因此“x”可以采用“FloorWax”和“DessertTopping”类型。您现在必须检查所有代码并找到该梳子的所有实例并修改它们。现在代码是“脆弱的”——需求的变化意味着大量的代码变化。在 OO 中,您正在努力使代码不那么脆弱。

OO 解决方案是使用多态性,您可以将其视为一种有限的鸭子类型:您定义了可以信任某事物执行的所有操作。你可以通过定义一个高级类来做到这一点,可能是抽象的,它具有低级类的所有方法。在 Java 中,像这样的类最好表示为“接口”,但它具有类的所有类型属性。实际上,您可以将接口视为一个承诺,即可以信任特定类“就好像”它是另一个类一样。

  public interface VeebleFeetzer { /* ... */ };
  public class FoodDispenser implements VeebleFeetzer { /* ... */ }
  public class MissleController implements VeebleFeetzer { /* ... */ }
  public class FloorWax implements VeebleFeetzer { /* ... */ }
  public class DessertTopping implements VeebleFeetzer { /* ... */ }

您现在所要做的就是使用对 VeebleFeetzer 的引用,编译器会为您计算出来。如果您碰巧添加了另一个属于 VeebleFeetzer 子类型的类,编译器将选择方法并检查讨价还价中的参数

VeebleFeetzer x;   // A reference to anything 
                   // that implements VeebleFeetzer

// Some code that assigns something to x

x.dropPellet();

// Some more code

x.launchAt("Moon");

 
    

【讨论】:

  • 好吧 instanceof 使强制转换安全,与 C 不同,因此在这种情况下您不会真正失去编译器的帮助。不过,这并没有使它成为一种糟糕的方式!
  • 另一个问题是,添加 FloorWax 类会迫使您访问与 FloorWax 无关的全部方法,并将“if (x instanceof FloorWax)”添加到开关中。
  • 您似乎使用强类型作为与静态类型相同的东西。这并不少见,但更常见的用途是在运行时检查强类型类型。 s="Year: "+2009 # TypeError 弱类型化就像C,你自己。这与静态或动态类型无关。 * Java,静态和强类型。 * Ruby & Pyton,动态和强类型。 * C,静态和弱类型。 en.wikipedia.org/wiki/…
【解决方案2】:

这与其说是鸭式打字,不如说是正确的面向对象风格;事实上,能够继承类 A 并在类 B 上调用相同的方法并让它做其他事情是语言中继承的全部意义。

如果您经常检查对象的类型,那么您要么太聪明了(尽管我认为这是鸭子打字爱好者喜欢的这种聪明,除了不那么脆弱的形式)或者您没有接受面向对象编程的基础知识。

【讨论】:

  • 然后以正确的方式使用鸭子打字,传递 Object* 的。使用 RTTI 使代码更少面向对象,以至于它破坏了局部性并使代码变得脆弱。
  • 我说了什么与此相矛盾的吗?
  • 喜欢维基百科的 RTTI 文章:“在最初的 C++ 设计中,Stroustrup 没有包含运行时类型信息,因为他认为这种机制经常被误用 [1]。”
【解决方案3】:

嗯……

如果我错了,请纠正我,但标记类和鸭子类型是两个不同的概念,尽管不一定相互排斥。

当一个人想要在类中使用标签来定义类型时,恕我直言,应该修改他们的类层次结构,因为这是一个明显的概念流血,抽象类需要知道实现细节阶级父母身份试图隐藏。你使用正确的模式吗?换句话说,您是否试图以一种不自然地支持它的模式强制行为?

鸭类型是一种松散定义类型的能力,只要定义了参数实例中的必要方法,方法就可以接受任何类型。然后该方法将使用该参数并调用必要的方法,而不会过多地打扰实例的父级。

所以这里......正如查理指出的那样,臭味暗示是使用 instanceof。很像静态或其他臭名昭著的关键字,每当它们出现时,人们必须问“我在这里做正确的事吗?”,并不是说它们天生就是错误的,而是它们经常被用来破解糟糕或不合适的 OO 设计。

【讨论】:

    【解决方案4】:

    我的一个回应是你失去了 OOP 的主要好处之一:多态性。这减少了开发新代码的时间(开发人员喜欢开发新代码,所以这应该有助于你的论点:-)

    如果在向现有系统添加新类型时,除了确定要构造哪个实例之外,还必须添加逻辑,那么,在 Java 中,你做错了(假设新类应该只是一个替换另一个)。

    通常,在 Java 中处理此问题的适当方法是保持代码多态并利用接口。因此,每当他们发现自己想要添加另一个变量或执行 instanceof 时,他们可能应该改为实现接口。

    如果您可以说服他们更改代码,那么将接口改造到现有代码库中是很容易的。就此而言,我会花时间用 instanceof 编写一段代码并将其重构为多态。如果能看到前后版本并进行比较,人们就更容易看清重点了。

    【讨论】:

      【解决方案5】:

      您可能希望向您的同事指出 Liskov 替换原则,这是 SOLID 的五个支柱之一。

      链接:

      【讨论】:

        【解决方案6】:

        当您说“强类型语言中的鸭子类型”时,实际上是指“在静态类型语言中模仿(子类型)多态性”。

        当您拥有不包含任何行为的数据对象 (DTO) 时,这并没有那么糟糕。如果您确实有一个成熟的 OO 模型(问问自己是否真的如此),那么您应该在适当的情况下使用该语言提供的多态性。

        【讨论】:

        • 宾果游戏。这就是我开车的目的。关于不变量可能在哪里发挥作用的任何想法?甚至数据对象也有规则。如果有它们的类型,规则似乎会有所不同。
        【解决方案7】:

        虽然我通常是像 python 这样的鸭式语言的粉丝,但我可以在 java 中看到你的问题。

        如果您正在编写将与此代码一起使用的所有类,那么您不需要鸭式类型,因为您不需要考虑代码不能直接继承自(或实现)一个接口或其他统一的抽象。

        duck-typing 的一个缺点是您需要在代码上运行额外的单元测试类:新类可能返回与预期不同的类型,从而导致其余代码失败。因此,尽管鸭式打字允许向后灵活性,但它需要大量的前瞻性思维来进行测试。

        简而言之,你有一个包罗万象的(硬)而不是一个包罗万象的(简单)。我认为这就是病态。

        【讨论】:

          【解决方案8】:

          为什么“模仿类层次结构”而不是设计和使用它?重构方法之一是用多态性替换“switch”es(链式 if 几乎相同)。为什么在多态性会导致代码更简洁的情况下使用开关?

          【讨论】:

            【解决方案9】:

            这不是鸭式打字,它只是在具有(或多或少)真正多态性的语言中模拟多态性的一种糟糕方法。

            【讨论】:

              【解决方案10】:

              回答标题问题的两个论据:

              1) Java 应该是“一次编写,随处运行”,因此当我们在某处更改环境时,为一个层次结构编写的代码不应抛出 RuntimeExceptions。 (当然,这条规则也有例外——双关语。)

              2) Java JIT 执行非常激进的优化,依赖于知道给定符号必须是一种类型并且只能是一种类型。解决此问题的唯一方法是强制转换。

              正如其他人所提到的,您的“实例”与我在这里回答的问题不匹配。任何类型、鸭子或静态的任何东西都可能存在您描述的问题。有更好的 OOP 方法来处理它。

              【讨论】:

                【解决方案11】:

                您可以使用 Method- 和 Strategy-Pattern 代替 instanceof,将代码混合在一起看起来比以前好多了...

                【讨论】:

                  猜你喜欢
                  • 2023-03-11
                  • 2014-01-13
                  • 1970-01-01
                  • 2016-05-10
                  • 2013-02-03
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-02-11
                  相关资源
                  最近更新 更多