空接口代码有异味吗?

时间:2011-09-26 08:46:47

标签: c# design-patterns

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

当然听起来不对。我只能通过坚持希望有一天这些类会有一些共同点来安慰自己,我会将这个共同的逻辑移到我的空接口上。然而,我并不满足于思考我是否应该采用两种不同的方法并且有条件地呼叫下一步。这会是一个更好的方法吗?

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

我的问题是:空接口是设计问题的强烈标志还是被广泛使用?

编辑:对于那些感兴趣的人,我后来发现功能语言中的歧视联盟是我想要实现的目标的完美解决方案。 C#对这个概念似乎并不友好。

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

5 个答案:

答案 0 :(得分:48)

虽然看起来存在一种设计模式(很多人已经提到了“标记界面”),但我相信这种做法的使用是代码气味的指示(大多数时候至少)。

正如@ V4Vendetta发布的那样,有一个针对此的静态分析规则: http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

  

如果您的设计包含期望实现类型的空接口,则可能使用接口作为标记或标识一组类型的方法。 如果此标识将在运行时发生,则完成此操作的正确方法是使用自定义属性。使用属性的存在或不存在或属性的属性来标识目标类型。 如果标识必须在编译时进行,则可以使用空接口。

这是引用的MSDN推荐:

  

删除界面或向其添加成员。如果使用空接口标记一组类型,请使用自定义属性替换该接口。

这也反映了已经发布的维基百科链接的批判部分。

  

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

答案 1 :(得分:9)

您声明您的功能“根据特定情况返回完全不同的对象” - 但它们有多么不同?可以是流编写器,另一个是UI类,另一个是数据对象吗?不......我对此表示怀疑!

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

答案 2 :(得分:8)

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

接口定义了实现者所遵循的契约 - 如果你有空的接口,你不使用反射(与标记接口一样),那么你也可以使用Object作为({已存在)基类型。

答案 3 :(得分:6)

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

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

答案 4 :(得分:4)

许多人可能已经说过,空接口确实可以用作“标记接口”。

我能想到的最好的用途可能是将对象表示为属于域的特定子集,由相应的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的通用存储库,然后您进行编译时检查,确保您的代码没有尝试将Security对象传递给Northwind DB(或任何其他排列)。在这种情况下,即使没有提供任何实施合同,界面也会提供有关课程性质的有价值信息。