What is wrong with testing an object to see if it implements an interface?
在这个答案的注释中指出,"检查对象是否实现了接口,尽管它可能很猖獗,这是一件坏事。"
以下是我认为这一做法的一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public interface IFoo { void Bar(); } public void DoSomething(IEnumerable<object> things) { foreach(var o in things) { if(o is IFoo) ((IFoo)o).Bar(); } } |
我的好奇心像以前使用过这种模式变化的人一样被激起,我寻找了一个很好的例子或解释为什么它是一件坏事,却找不到。
虽然很可能我误解了评论,但有人能给我举个例子或链接来更好地解释评论吗?
这取决于你想做什么。有时可能是适当的-例如:
- linq-to-objects,用于优化
Count 之类的操作,这些操作可以通过专门的成员在IList 上更有效地执行。 - linq-to-xml,用于提供一个非常友好的API,它接受各种类型,并在适当的时候迭代值。
- 如果希望在Windows窗体中查找特定控件下某一类型的所有控件,则需要检查每个控件是否为容器,以确定是否重复。
在其他情况下,它不太合适,您应该考虑是否可以更改参数类型。这绝对是一种"味道"——通常,您不应该关心已经交付给您的任何东西的实现细节;您应该只使用声明的参数类型提供的API。这也被称为违反了里斯科夫替代原理。
不管周围那些武断的开发人员怎么说,有时候你只是想检查一个对象的执行时间类型。不使用
我个人更愿意编写您这样显示的代码,注意:
1 2 3 4 5 6 7 | public void DoSomething(IEnumerable<object> things) { foreach(var foo in things.OfType<IFoo>()) { foo.Bar(); } } |
它完成了同样的事情,但以一种整洁的方式:)
我希望这个方法看起来像这样,看起来更安全:
1 2 3 4 5 6 7 | public void DoSomething(IEnumerable<IFoo> things) { foreach(var o in things) { o.Bar(); } } |
阅读关于所提到的违反里斯科夫原理的文章:什么是里斯科夫替代原理?
如果你想知道评论人为什么要发表评论,最好让他们解释一下。
我不会认为你发布的代码是"坏的"。更"真实"的错误做法是使用接口作为标记。也就是说,您并不打算实际使用接口的方法;相反,您已经将类上的接口声明为以某种方式描述它的一种方法。使用属性而不是接口作为类上的标记。
标记接口在很多方面都是有害的。我曾经遇到的一个现实情况是:http://blogs.msdn.com/b/ericlippert/archive/2004/04/05/108086.aspx
也就是说,C编译器本身在一种情况下使用"标记接口"。Mads在这里讲述故事:http://blogs.msdn.com/b/madst/archive/2006/10/10/what-is-a-collection_3f00_uux
一个原因是,如果不挖掘代码,就无法立即看到对该接口的依赖。
声明
checking whether the object has implemented the interface , rampant
as it may be, is a bad thing
在我看来太武断了。正如其他人所回答的,您很可能能够将一组ifoo传递给您的方法,并获得相同的结果。
然而,接口对于向类中添加可选功能是有用的。例如,.NET框架提供IDataErrorInfo接口*。当实现此功能时,它向使用者表明,除了类的标准功能之外,它还可以提供错误信息。
在这种情况下,错误信息是可选的。WPF视图模型可能提供错误信息,也可能不提供错误信息。如果不查询接口,如果没有具有巨大表面积的基类,则不可能实现此可选功能。
*我们暂时忽略IDataErrorInfo接口的糟糕设计。
型
我不喜欢这种"打开类型"的编码方式,原因有两个。(我的行业、游戏开发方面的例子。提前道歉。:))
首先也是最重要的一点,我认为拥有一个异构的项目集合是草率的。例如,我可以收集"任何地方的一切",但是当重复收集以应用子弹效果、火力伤害或敌人的人工智能时,我必须浏览这个列表,这是我最不关心的东西。有单独的子弹、熊熊烈火和敌人的集合是"更清洁"的。请注意,我没有理由不能在多个集合中拥有一个单一的项目;一个单一燃烧的机器人导弹可以在所有三个列表中被引用,以根据它需要运行的三种逻辑类型来做它的"更新"部分。除了拥有"引用所有内容的单个集合"之外,我认为包含所有地方的所有内容的集合并不是非常有用;除非您查询列表中的任何内容,否则无法对其执行任何操作。
我讨厌做不必要的工作。这确实与上面的内容有关,但是当您创建一个给定的对象时,您知道它的功能是什么(或者可以在此时查询它们),所以您也可以利用当时的机会将它们放入正确的更具体的集合中。你有16毫秒的时间来处理世界上所有的事情,你想浪费时间处理、查询和从一般的事情中选择,还是你想开始做生意,只对你关心的具体事情进行操作?
在我的经验中,将代码库从异构数据集的一般操作转换为具有同构数据集的代码库,不仅提高了性能,而且还提高了理解力,因为更简单的代码可以做更明显的工作,而且通常减少了执行任何给定任务所需的代码量。
所以,说查询接口不好是一种教条式的说法,但如果你能弄清楚如何避免查询任何东西,这确实让事情变得简单了。至于我的"表现"声明和计数器,"如果你不衡量它,你就不能说任何关于它的事情,"应该很明显,不做什么比做它更快。不管这对单个项目、程序员或函数是否重要,都取决于拥有编辑器的人,但是如果我可以简化代码,并且这样做可以使代码在相同结果下工作更少,那么我将在不进行度量的情况下完成它。
如果方法要求插入接口的实例,则无论实现如何,都应该对其进行相同的处理。在您的示例中,一般不会有对象的通用列表,但是一个
型
我一点也不认为这是一件"坏事情",至少它本身没有。这段代码只是"z中所有y元素的x"的文字转录,在需要这样做的情况下,这是完全可以接受的。为了简洁起见,您当然可以使用
反对它的主要原因是,根据OOP神学,接口旨在模拟对象可以替代的不同类型的"黑盒"。预测一个实现接口的算法构成了对该接口中的算法的移动行为。
本质上,界面是行为角色。如果您认为OOP是一个好主意,那么您应该只使用接口来建模行为,这样算法就不必这样做。我不认为现在的OOP是个好主意,所以我的答案是有用的。