在this answer的评论中,声明“ 检查对象是否已实现接口 ,尽可能猖獗,一件坏事“
以下是我认为这种做法的一个例子:
public interface IFoo
{
void Bar();
}
public void DoSomething(IEnumerable<object> things)
{
foreach(var o in things)
{
if(o is IFoo)
((IFoo)o).Bar();
}
}
由于我的好奇心激起了以前曾使用过这种模式变体的人,我搜索了一个很好的例子或解释为什么它是一件坏事并且无法找到它。
虽然我很可能误解了评论,但有人可以提供一个示例或链接来更好地解释评论吗?
答案 0 :(得分:36)
这取决于你想要做什么。有时它可能是合适的 - 例子可能包括:
Count
等操作,可以通过专门的成员在IList<T>
上更有效地执行。在其他情况下,它不太合适,您应该考虑是否可以更改参数类型。这绝对是一种“嗅觉” - 通常你不应该关注任何交给你的实现细节;您应该只使用声明的参数类型提供的API。这也称为违反Liskov Substitution Principle。
无论教条开发者可能会说什么,有时你只是做想要检查一个对象的执行时间类型。如果不使用object.Equals(object)
/ is
/ as
,很难正确覆盖GetType
,例如:)它并不总是坏事,但它总是会让你考虑是否有更好的方法。谨慎使用,只有在真正最合适的设计中使用。
我个人宁愿写下你这样看过的代码,请注意:
public void DoSomething(IEnumerable<object> things)
{
foreach(var foo in things.OfType<IFoo>())
{
foo.Bar();
}
}
它完成同样的事情,但以更简洁的方式:)
答案 1 :(得分:18)
我希望这个方法看起来像这样,看起来更安全:
public void DoSomething(IEnumerable<IFoo> things)
{
foreach(var o in things)
{
o.Bar();
}
}
阅读有关Liskov原则的违反行为:What is the Liskov Substitution Principle?
答案 2 :(得分:10)
如果你想知道评论者为何发表评论,可能最好请他们解释。
我不认为你发布的代码是“坏”。更“真正”的不良做法是将接口用作标记。也就是说,你没有计划实际使用接口的方法;相反,您已经在类上声明了接口,以某种方式描述它。 使用属性而非接口作为类上的标记。
标记界面在许多方面都是危险的。我曾经遇到过一个真实的情况,一个重要的产品在标记界面的基础上做出了错误的决定: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_.aspx
答案 3 :(得分:6)
一个原因是,如果没有深入研究代码,就会对该界面产生依赖性。
答案 4 :(得分:4)
声明
检查对象是否已实现接口,猖獗 可能是,这是一件坏事
在我看来过于教条。正如其他人已经回答的那样,您可能能够将IFoo的集合传递给您的方法并获得相同的结果。
但是,界面对于向类添加可选功能非常有用。例如.net框架提供IDataErrorInfo接口*。实施此功能后,它向消费者表明除了类标准功能外,还可以提供错误信息。
在这种情况下,错误信息是可选的。 WPF视图模型可能会也可能不会提供错误信息。如果没有查询接口,如果没有具有巨大表面积的基类,这种可选功能将无法实现。
* 我们暂时忽略了IDataErrorInfo接口的可怕设计。
答案 5 :(得分:1)
如果您的方法要求您注入接口的实例,则无论实现如何,都应该将其视为相同
在您的示例中,您通常不会有通用的对象列表,但是ISomething
的列表和调用ISomething.Bar()
的列表将由具体类型实现,因此称之为实现。如果该实现无效,那么您不必进行检查。
答案 6 :(得分:1)
出于几个原因,我不喜欢整个“开启类型”编码风格。 (与我的行业,游戏开发有关的例子。提前道歉。:))
首先,我认为拥有异构的物品集是很草率的。例如。我可以拥有“无处不在”的集合,但是当迭代集合以应用子弹效果或火焰伤害或敌人AI时,我必须走这个列表,这主要是我不关心的东西。拥有单独的子弹,熊熊烈火和敌人的集合,更加“清洁”恕我直言。请注意,我无法在多个集合中拥有单个项目;可以在所有这三个列表中引用单个燃烧的机器人导弹,以根据需要运行的三种类型的逻辑来执行其“更新”的部分。除了“一个引用一切的单一集合”之外,我认为包含所有地方的集合并不是非常有用;你不能对列表中的任何内容做任何事情,除非你查询它能做什么。
我讨厌做不必要的工作。这与上面的内容非常相关,但是当你创建一个给定的东西时,你知道它的功能是什么(或者可以在那个时候查询它们),所以你当时也可以利用这个机会将它们放在正确的更具体的集合中。你有16ms来处理世界上的一切,你想浪费你的时间来处理,查询和选择通用的东西,或者你是否想要开展业务并只根据你关心的具体事情进行操作?
根据我的经验,将代码库从异构数据集上的泛型操作转换为具有同类数据集的代码库,不仅导致性能提高,而且导致更简单的代码执行更明显的工作并且通常减少数量的理解增加执行任何特定任务所需的代码。
所以是的,说查询界面很糟糕是教条主义,但如果你能弄清楚如何避免需要查询任何内容,它似乎会使事情更简单。至于我的“表现”陈述和“如果你不衡量它,你就不能说出任何事情”的反击,那么显而易见的是,不做某事比做它更快。这对于单个项目,程序员或函数是否重要取决于具有编辑器的人员,但是如果我可以简化代码并且在这样做的同时使其为相同的结果做更少的工作,我将要做的它没有费心去测量。
答案 7 :(得分:0)
我认为这根本不是“坏事”,至少本身并不是这样。代码只是 x z 中所有 y 的文字转录,并且在您需要这样做的情况下,这完全可以接受。当然,为简洁起见,您可以使用things.OfType<Foo>()
。
根据OOP神学,推荐反对它的主要原因是接口用于模拟对象可以替换的不同类型的“黑盒子”。预测一个关于实现接口的算法构成了应该在该接口中的算法的移动行为。
基本上,界面是一种行为角色。如果您认为OOP是个好主意,那么您应该仅使用接口来模拟行为,因此算法不必这样做。我不认为这些天通过OOP的事实上是个好主意,所以这就是我的答案可能有用。