2011-09-26 62 views
64

我有一个函数返回相同种类的对象(查询结果),但没有属性或方法的共同点。为了有一个通用的类型,我使用了一个空接口作为返回类型,并在两者上“实现”。空接口代码味道?

这听起来不对。我只能抱紧自己,希望有一天这些类会有共同的东西,我会把这个共同的逻辑移到我的空白界面。然而,我并不满意,并在考虑是否应该采用两种不同的方法,并有条件地接下来打电话。这会是一个更好的方法吗?

我也被告知.NET Framework使用空接口进行标记目的。

我的问题是:是一个空的界面设计问题的一个强有力的标志或被广泛使用?

编辑:对于那些有兴趣的人,我后来发现,功能语言中的歧视工会是我尝试实现的完美解决方案。 C#对这个概念看起来并不友善。

编辑:我写了一个关于这个问题的longer piece,详细解释了这个问题和解决方案。

+14

这些被称为[标记接口](http://en.wikipedia.org/wiki/Marker_interface_pattern),显然它们被广泛使用。 – BoltClock

+3

阅读此http://msdn.microsoft.com/en-us/library/ms182128%28v=vs.80%29.aspx(我的意见需要不同的方法) – V4Vendetta

+1

东西在类似的线http:// stackoverflow。 com/questions/835140/using-marker-classes-to-control-logic-flow – V4Vendetta

回答

39

虽然看起来这个用例存在一种设计模式(现在很多人提到过“标记接口”),但我相信这种做法的使用是代码气味的指示(大部分时间在最小)。

正如@ V4Vendetta贴,还有的是,针对这种静态分析规则: http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

如果您的设计包括类型有望实现,你可能正在使用一个接口作为标记或空接口一种识别一组类型的方法。如果此标识将在运行时发生,则正确的方法是使用自定义属性。使用属性的存在或不存在或属性的属性来标识目标类型。 如果标识必须在编译时发生,那么使用空接口是可以接受的。

这是引述MSDN推荐:

取下接口或成员添加到它。如果正在使用空接口标记一组类型,请使用自定义属性替换该接口。

这也反映了已发布维基百科链接的评论部分。

标记接口的一个主要问题是接口定义了实现类的契约,并且该契约被所有子类继承。这意味着你不能“实现”一个标记。在给出的例子中,如果你创建了一个你不想序列化的子类(可能是因为它依赖于临时状态),你必须求助于显式抛出NotSerializableException(每个ObjectOutputStream文档)。

+1

我认为在我的使用案例(只有两个类,不可能派生),它似乎没问题。查看我的澄清编辑。 –

+0

我接受这个答案。尽管其他答案也提供了有价值的信息,但这篇文章主要阐述了该方法潜在的问题。 –

+1

正如在其他地方已经指出的那样,虽然'属性'是这样做的'正确'方式,但它们实施和使用会更加尴尬,实际上这会妨碍它们的使用。 – nicodemus13

6

你回答了你自己的问题......“我有一个函数根据某些情况返回完全不同的对象。”...为什么你想要有相同的函数返回完全不同的对象?我看不出有用的理由,也许你有一个好的,在这种情况下,请分享。

编辑:考虑到你的澄清,你应该确实使用标记界面。 “完全不同”与“同一种”完全不同。如果他们完全不同(不仅仅是他们没有共享成员),那将是一种代码味道。

+0

这看起来像一个更好的候选人作为评论。我在我的问题中添加了一个澄清部分。如果您需要其他任何东西,请提问。 –

7

如果没有用作marker interface,我会说是的,这是一种代码异味。

一个接口定义了实现者遵守的契约 - 如果你有没有使用反射的空接口(就像标记接口一样),那么你可以使用Object作为(已经存在的)基本类型。

+1

'object'将会过于通用,并且不会提供返回“kind”的提示。界面帮助我缩小解释函数返回值的选项。但我理解你的评论与缺乏关于我要返回的对象的相似性的澄清有关。 –

+0

@ssg - 挺。如果您将这些对象传递给一个方法来对它们进行操作,则必须有与这些对象相似的东西,否则您应该有单独的方法。 – Oded

9

你说你的函数“根据某些情况返回完全不同的对象” - 但它们有多不同?能不能是一个流编写器,另一个是UI类,另一个是数据对象?不......我怀疑它!

您的对象可能没有任何常用的方法或属性,但它们在角色或用法上可能相似。在这种情况下,marker interface似乎完全合适。

+0

他们是不同类型的查询结果,但都是查询结果,是的。 –

+1

行 - 我认为,他们有一个共同的角色,即使他们不包含共同的属性。听起来像一个标记界面是正确的! – ColinE

3

正如很多人可能已经说过的那样,空接口确实可以有效地用作“标记接口”。

我能想到的最好的用法可能是将对象表示为属于特定子域的对象,由相应的Repository处理。假设你有不同的数据库来检索数据,并且你有一个Repository实现。一个特定的Repository只能处理一个子集,不应该从任何其他子集获得一个对象的实例。你的域模型可能是这样的:那么

//Every object in the domain has an identity-sourced Id field 
public interface IDomainObject 
{ 
    long Id{get;} 
} 

//No additional useful information other than this is an object from the user security DB 
public interface ISecurityDomainObject:IDomainObject {} 

//No additional useful information other than this is an object from the Northwind DB 
public interface INorthwindDomainObject:IDomainObject {} 


//No additional useful information other than this is an object from the Southwind DB 
public interface ISouthwindDomainObject:IDomainObject {} 

您的仓库可以制成通用的ISecurityDomainObject,INorthwindDomainObject和ISouthwindDomainObject,然后你有一个编译时检查你的代码是不是试图通过一个安全对象为Northwind DB(或任何其他排列)。在这种情况下,即使没有提供任何实施合同,界面也会提供有关班级性质的宝贵信息。