空接口是代码的味道吗?

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

听起来不太对劲。我只能通过坚持希望有一天这些类会有共同点来安慰自己,我将把这些共同的逻辑移到我的空接口上。然而,我并不满意,并且在考虑是否应该有两个不同的方法,然后有条件地调用下一个。这是个更好的方法吗?

我还被告知.NETFramework 使用空接口来标记目的。

我的问题是: 一个空的界面是一个设计问题的强烈标志还是它被广泛使用?

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

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

39083 次浏览

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.

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.

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.

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).

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.