【问题标题】:Are empty interfaces code smell? [closed]空接口代码有异味吗? [关闭]
【发布时间】:2011-09-26 08:46:47
【问题描述】:

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

这听起来当然不对。我只能通过坚持希望有一天这些类会有一些共同点来安慰自己,我会将这种共同逻辑移到我的空界面中。然而我并不满意,我在考虑是否应该有两种不同的方法并有条件地调用 next。那会是更好的方法吗?

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

我的问题是:空界面是设计问题的强烈迹象还是被广泛使用?

编辑:对于那些感兴趣的人,我后来发现函数式语言中的可区分联合是我想要实现的完美解决方案。 C# 似乎对这个概念并不友好。

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

【问题讨论】:

标签: c# design-patterns


【解决方案1】:

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

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

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

这是引用的 MSDN 推荐:

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

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

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

【讨论】:

  • 我认为在我的用例中(只有两个类,不太可能派生),它似乎没问题。请参阅我的澄清编辑。
  • 我接受这个作为答案。尽管其他答案也提供了有价值的信息,但这个答案最详细地说明了该方法的潜在问题。
  • 正如在其他地方指出的那样,尽管attributes 是这样做的“正确”方式,但它们的实施和使用更加尴尬,实际上这将不利于它们的使用。跨度>
【解决方案2】:

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

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

【讨论】:

  • 它们是不同类型的查询结果,但都是查询结果,是的。
  • 好的——正如我所想,它们有一个共同的角色,即使它们不包含共同的属性。听起来像一个marker界面刚刚好!
【解决方案3】:

如果不用作marker interface,我会说是的,这是代码异味。

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

【讨论】:

  • object 过于笼统,不会提供有关返回“种类”的提示。接口帮助我缩小解释函数返回值的选择范围。但我理解您对我返回的对象的相似性缺乏澄清的评论。
  • @ssg - 相当。如果你将这些对象传递给一个方法来操作它们,那么它们必须有与这些对象相似的一些东西,否则你应该有单独的方法。
【解决方案4】:

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

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

【讨论】:

  • 这看起来更适合发表评论。我在我的问题中添加了一个澄清部分。如果您需要其他任何东西,请询问。
【解决方案5】:

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

可能我能想到的最佳用途是将对象表示为属于域的特定子集,由相应的存储库处理。假设您有不同的数据库从中检索数据,并且每个数据库都有一个 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(或任何其他排列)。在这种情况下,即使接口没有提供任何实现契约,它也会提供有关类性质的有价值信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-04
    • 1970-01-01
    • 2011-02-02
    • 2019-05-18
    • 1970-01-01
    • 2010-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多