我在一个Java程序员团队工作.我的一位同事不时建议我做"只添加一个类型字段"(usu."String type").或者代码将承载" if (foo instanceof Foo){...} else if( foo instanceof Bar){...}
".
尽管如此,乔什布洛赫的警告"标记的类是一个模仿正确的类层次结构",我对这种事情的一线回应是什么?那么我该如何更认真地阐述这个概念呢?
我很清楚 - 上下文是Java - 正在考虑的对象类型就在我们的集体面前 - IOW:紧跟在"class","enum"或"interface"之后的单词等.
但除了难以证明或量化(在现场)"它使你的代码更复杂",我怎么说"在一个(或多或少)强类型语言中打鸭是一个愚蠢的想法,提出了更深层次的设计病理学?
实际上,你说它在那里相当不错.
事实上,梳子的"实例"几乎总是一个坏主意(例如,当您进行编组或序列化时会发生例外情况,在短时间内您可能没有掌握所有类型信息.)正如乔什所说,这是一个坏的阶级等级的标志.
你知道这是一个坏主意的方式是它使代码变得脆弱:如果你使用它,并且类型层次结构发生变化,那么它可能会在它发生的任何地方打破这个实例.更重要的是,你失去了强打字的好处; 编译器无法提前捕获错误.(这有点类似于C中的类型转换引起的问题.)
让我稍微扩展一下,因为从评论来看,我不太清楚.你在C中使用类型转换的原因,或者instanceof
你想说"似乎"的原因:使用foo
它就好像它是一个bar
.现在,在C中,根本没有运行时类型信息,所以你只是在没有网络的情况下工作:如果你对某些东西进行类型转换,生成的代码会将该地址视为包含特定类型,无论是什么,你应该只希望它会导致运行时错误,而不是默默地破坏某些东西.
鸭子打字只是提高了一个标准; 在像Ruby或Python或Smalltalk这样的动态弱类型语言中,一切都是无类型的引用; 你在运行时向它发送消息,看看会发生什么.如果它理解一个特定的消息,它"像鸭子一样走路" - 它处理它.
这可以非常方便和有用,因为它允许奇怪的黑客攻击,例如将生成器表达式分配给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是FoodDispenser
[1]或MissleController
[2] ,你就可以了.否则热潮.或者意外地,没有繁荣.
在您的描述,您使用的梳子保护自己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");
这不是鸭子打字,因为它只是适当的面向对象的风格; 实际上,能够继承类A并在类B上调用相同的方法并让它做其他事情是语言中的整个继承点.
如果你经常检查一个物体的类型,那么你要么太聪明了(虽然我认为这就是这种聪明才能打字的爱好者喜欢,除了一个不那么脆弱的形式)或者你没有接受对象的基础知识面向编程.