空接口有代码味道吗?

发布于 2024-12-06 15:13:07 字数 521 浏览 1 评论 0原文

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

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

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

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

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

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

I have a function that returns same kind of objects (query results) but with no properties or methods in common. In order to have a common type I resorted using an empty interface as a return type and "implemented" that on both.

That doesn't sound right of course. I can only console myself by clinging to hope that someday those classes will have something in common and I will move that common logic to my empty interface. Yet I'm not satisfied and thinking about whether I should have two different methods and conditionally call next. Would that be a better approach?

I've been also told that .NET Framework uses empty interfaces for tagging purposes.

My question is: is an empty interface a strong sign of a design problem or is it widely used?

EDIT: For those interested, I later found out that discriminated unions in functional languages are the perfect solution for what I was trying to achieve. C# doesn't seem friendly to that concept yet.

EDIT: I wrote a longer piece about this issue, explaining the issue and the solution in detail.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

绮筵 2024-12-13 15:13:07

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

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

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

这是引用的 MSDN 建议:

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

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

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

Although it seems there exists a design pattern (a lot have mentioned "marker interface" now) for that use case, i believe that the usage of such a practice is an indication of a code smell (most of the time at least).

As @V4Vendetta posted, there is a static analysis rule that targets this:
http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

If your design includes empty interfaces that types are expected to implement, you are probably using an interface as a marker or a way to identify a group of types. If this identification will occur at run time, the correct way to accomplish this is to use a custom attribute. Use the presence or absence of the attribute, or the properties of the attribute, to identify the target types. If the identification must occur at compile time, then it is acceptable to use an empty interface.

This is the quoted MSDN recommendation:

Remove the interface or add members to it. If the empty interface is being used to label a set of types, replace the interface with a custom attribute.

This also reflects the Critique section of the already posted wikipedia link.

A major problem with marker interfaces is that an interface defines a contract for implementing classes, and that contract is inherited by all subclasses. This means that you cannot "unimplement" a marker. In the example given, if you create a subclass that you do not want to serialize (perhaps because it depends on transient state), you must resort to explicitly throwing NotSerializableException (per ObjectOutputStream docs).

可爱暴击 2024-12-13 15:13:07

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

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

You state that your function "returns entirely different objects based on certain cases" - but just how different are they? Could one be a stream writer, another a UI class, another a data object? No ... I doubt it!

Your objects might not have any common methods or properties, however, they are probably alike in their role or usage. In that case, a marker interface seems entirely appropriate.

混吃等死 2024-12-13 15:13:07

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

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

If not used as a marker interface, I would say that yes, this is a code smell.

An interface defines a contract that the implementer adheres to - if you have empty interfaces that you don't use reflection over (as one does with marker interfaces), then you might as well use Object as the (already existing) base type.

惯饮孤独 2024-12-13 15:13:07

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

编辑:考虑到您的澄清,您确实应该使用标记接口。 “完全不同”与“同类”有很大不同。如果它们完全不同(不仅仅是它们没有共享成员),那就会产生代码味道。

You answered your own question... "I have a function that returns entirely different objects based on certain cases."... Why would you want to have the same function that returns completely different objects? I can't see a reason for this to be useful, maybe you have a good one, in which case, please share.

EDIT: Considering your clarification, you should indeed use a marker interface. "completely different" is quite different than "are the same kind". If they were completely different (not just that they don't have shared members), that would be a code smell.

桜花祭 2024-12-13 15:13:07

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

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

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

As many have probably already said, an empty interface does have valid use as a "marker interface".

Probably the best use I can think of is to denote an object as belonging to a particular subset of the domain, handled by a corresponding Repository. Say you have different databases from which you retrieve data, and you have a Repository implementation for each. A particular Repository can only handle one subset, and should not be given an instance of an object from any other subset. Your domain model might look like this:

//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 {}

Your repositories can then be made generic to ISecurityDomainObject, INorthwindDomainObject, and ISouthwindDomainObject, and you then have a compile-time check that your code isn't trying to pass a Security object to the Northwind DB (or any other permutation). In situations like this, the interface provides valuable information regarding the nature of the class even if it does not provide any implementation contract.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文