我在一个Java程序员团队工作。我的一位同事不时建议我做“只添加一个类型字段”(usu。“String type”)。或者代码将承载“if (foo instanceof Foo){...} else if( foo instanceof Bar){...}
”。
乔什·布洛赫(Josh Bloch)告诫说,“标记的课程是对正确的阶级等级的模仿”,尽管如此,我对这类事情的一线回应是什么?那么我该如何更认真地阐述这个概念呢?
我很清楚 - 上下文是Java - 正在考虑的对象类型正好在我们的集体面前 - IOW:紧跟在“class”,“enum”或“interface”之后的单词等。
但除了难以证明或量化(在现场)“它使你的代码更复杂”,我怎么说“用一种(或多或少)强类型语言打鸭子是一种愚蠢的想法暗示了更深层次的设计病理学?
答案 0 :(得分:8)
实际上,你说的很合理。
事实是,梳子的“实例”几乎总是一个坏主意(例如,当您进行编组或序列化时会发生异常,当您在短暂的时间间隔内可能没有掌握所有类型信息时。)正如乔什所说,这是一种不良的阶级等级的标志。
你知道的方式这是一个坏主意是它使代码变得脆弱:如果你使用它,并且类型层次结构更改,那么它可能会打破它发生在任何地方的梳子实例。更重要的是,你失去了强打字的好处;编译器无法提前捕获错误。 (这有点类似于C中的类型转换引起的问题。)
让我稍微扩展一下,因为从评论来看,我不太清楚。您在C或instanceof
中使用类型转换的原因,您想要“好像”:请使用此foo
,就好像它是bar
一样。现在,在C中,根本没有运行时类型信息,所以你只是在没有网络的情况下工作:如果你对某些东西进行类型转换,生成的代码会将该地址视为包含特定类型,无论是什么,你应该希望它会导致运行时错误,而不是默默地破坏某些东西。
这可以非常方便和有用,因为它允许奇怪的黑客攻击,例如将生成器表达式分配给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 if
和instanceof
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");
答案 1 :(得分:6)
这不是鸭子打字,因为它只是适当的面向对象风格;实际上,能够继承类A并在类B上调用相同的方法并让它做其他事情就是语言中的整个继承点。
如果你经常检查一个物体的类型,那么你要么太聪明了(尽管我认为这种聪明才能让爱好者喜欢,除了一种不那么脆弱的形式)或者你没有拥抱面向对象编程的基础知识。
答案 2 :(得分:3)
嗯...
如果我错了,请纠正我,但是标记的类和鸭子类型是两个不同的概念,尽管不一定非常排他性。
当一个人想要在类中使用标签来定义类型时,恕我直言,应该修改他们的类层次结构,因为它是一个清晰的概念流失,其中抽象类需要知道实现细节。阶级父母试图隐藏。你使用的是正确的图案吗?换句话说,你是否试图以一种自然不支持它的模式来强迫行为?
鸭子类型是松散定义类型的能力,只要定义了参数实例中的必要方法,方法就可以接受任何类型。然后,该方法将使用该参数并调用必要的方法,而不必过多地打扰实例的父级。
所以这里......正如查理所指出的那样,臭臭的暗示是使用了instanceof。就像静态或其他有臭味的关键词一样,每当它们出现时,必须要问“我在这里做正确的事吗?”,并不是说它们是错误的,但它们经常被用来破解一个坏的或不合适的OO设计。
答案 3 :(得分:2)
我的一线回应是你失去了OOP的主要好处之一:多态性。这减少了开发新代码的时间(开发人员喜欢开发新代码,因此这应该有助于您的论点: - )
如果在向现有系统添加新类型时,您必须添加逻辑,除了确定要构造哪个实例之外,然后在Java中,您做错了(假设新类应该只是一个替换另一个)。
通常,在Java中处理此问题的适当方法是保持代码的多态性并使用接口。因此,只要他们发现自己想要添加另一个变量或者执行一个实例,他们就应该实现一个接口。
如果您可以说服他们更改代码,那么将接口改装到现有代码库中非常容易。就此而言,我会花时间用instanceof来获取一段代码并将其重构为多态。如果人们能够看到之前和之后的版本并进行比较,那么人们就更容易看到这一点。
答案 4 :(得分:2)
答案 5 :(得分:1)
虽然我通常喜欢像python这样的鸭式语言,但我可以在java中看到它的问题。
如果您正在编写将与此代码一起使用的所有类,那么您不需要使用duck-type,因为您不需要允许代码无法直接继承的情况(或实现)接口或其他统一抽象。
鸭子类型的缺点是你需要在你的代码上运行一个额外的单元测试类:一个新类可以返回一个不同于预期的类型,然后导致其余的代码失败。因此,虽然鸭子类型允许向后灵活性,但它需要大量的前瞻性思维来进行测试。
总之,你有一个全能(硬)而不是少量(简单)。我认为这就是病理学。
答案 6 :(得分:1)
为什么“模仿类层次结构”而不是设计和使用它?其中一种重构方法是用多态性替换“switch”(链接ifs几乎相同)。为什么使用多态性会导致代码更清晰的交换机呢?
答案 7 :(得分:1)
当你说“用强类型语言打字”时,你实际上是指“在静态类型语言中模仿(子类型)多态”。
当您拥有不包含任何行为的数据对象(DTO)时,并不是那么糟糕。当你有一个完整的OO模型(问问自己是否真的如此)时,你应该使用适当的语言提供的多态性。
答案 8 :(得分:1)
这不是鸭子打字,它只是在一种具有(或多或少)真正多态性的语言中模拟多态的一种坏方法。
答案 9 :(得分:0)
回答标题问题的两个论点:
1)Java应该是“一次编写,随处运行”,因此当我们在某处更改环境时,为一个层次结构编写的代码不应该抛出RuntimeExceptions。 (当然,也有例外 - 双关 - 这个规则。)
2)Java JIT执行非常积极的优化,这些优化依赖于知道给定符号必须只有一种类型和一种类型。解决这个问题的唯一方法就是施放。
正如其他人所说,你的“实例”与我在这里回答的问题不符。任何类型,鸭子或静态的东西都可能有您描述的问题。有更好的OOP方法来处理它。
答案 10 :(得分:0)
您可以使用方法和策略模式代替实例,将代码混合在一起看起来比以前好多了......