空接口代码是否有异味?[关闭]


76

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

那听起来当然不对。我只能抱有希望,希望有一天这些类将有一些共同点,并将这种共同的逻辑移到我的空接口上,以此来安慰自己。但是我不满意,正在考虑是否应该使用两种不同的方法并有条件地调用next。那会是更好的方法吗?

有人还告诉我.NET Framework使用空接口进行标记。

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

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

编辑:我写了一篇关于这个问题的较长文章,详细解释了这个问题和解决方案。


15
这些被称为标记器接口,显然它们已被广泛使用。
BoltClock

3
阅读此msdn.microsoft.com/zh-cn/library/ms182128%28v=vs.80%29.aspx(我认为需要使用不同的方法)
V4Vendetta 2011年


9
代码气味可以是一种很好的气味吗,在这种情况下可以吗?例如咖啡,华夫饼和培根
克里斯S

当调用函数时,您是否已经从参数中知道给出了哪种对象?如果是这样,具有多个返回正确类型的函数会更正确。
Angel O'Sphere 2011年

Answers:


48

尽管似乎存在该用例的设计模式(现在很多人都提到了“标记接口”),但我相信这种做法的使用表明了代码的味道(至少在大多数情况下是这样)。

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

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

这是引用的MSDN建议:

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

这也反映了已经发布的Wikipedia链接的Critique部分。

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


1
我认为在我的用例中(仅两个类,不太可能派生),这似乎还可以。参见我的说明编辑。
Sedat Kapanoglu 2011年

我接受这个作为答案。尽管其他答案也提供了有价值的信息,但该答案最能说明该方法的潜在问题。
Sedat Kapanoglu 2011年

1
正如其他地方所指出的那样,尽管这attributes是“正确”的方式,但它们在实施和使用时比较笨拙,实际上在实践中会不利于其使用。
nicodemus13 2012年

9

您声明函数“根据某些情况返回完全不同的对象”-但是它们有什么不同?一个人可以是流作家,另一个可以是UI类,另一个可以是数据对象吗?不,我对此表示怀疑!

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


它们是不同类型的查询结果,但是两个查询结果都是。
Sedat Kapanoglu 2011年

1
好的-我认为,即使它们不包含通用属性,它们也具有通用角色。听起来像标记界面就对了!
ColinE 2011年

8

如果不用作标记接口,我会说是的,这是一种代码味道。

接口定义了实现者要遵守的契约-如果您有不使用反射的空接口(如标记接口那样),那么您也可以使用Object(已经存在的)基本类型。


2
object太笼统了,不会提示返回“种类”。接口可以帮助我缩小解释函数返回值的选项。但我理解您的评论,因为我对所返回对象的相似性缺乏澄清。
Sedat Kapanoglu 2011年

@ssg-很好。如果将这些对象传递给一个方法以对其进行操作,则这些对象必须具有类似的东西,否则,您应该有单独的方法。
Oded

6

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

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


这看起来像是更好的评论对象。我在问题中添加了一个澄清部分。询问是否还有其他需要。
Sedat Kapanoglu 2011年

4

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

我可能想到的最好的用法是将对象表示为属于该域的特定子集,并由相应的存储库处理。假设您有从中检索数据的不同数据库,并且每个数据库都有一个存储库实现。特定的存储库只能处理一个子集,不应为任何其他子集提供对象的实例。您的域模型可能如下所示:

//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(或任何其他排列)。在这种情况下,即使接口不提供任何实现合同,该接口也会提供有关类性质的有价值的信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.