寻找多继承接口(非实现)必要的例子/案例

4

C#和Java都支持接口从多个父级继承:

public interface IMyInterface : IInterface1, IInterface2

public interface MyInterface
extends Interface1, Interface2

我已经在.NET Framework和JavaAPI文档中寻找了多个接口继承的例子。
在JavaAPI中,我发现一些子接口继承自超级接口和一个或多个“标记”接口(即没有定义方法的接口)。所以,“标记”接口并没有真正强制实现任何东西。我可以看到这种做法的好处,特别是在注解被引入之前。
在.NET中,我发现接口继承自已经相关的多个父级。例如,IList继承自ICollectionIEnumerable,但ICollection已经是IEnumerable的后代。因此,通过单一继承就已经实现了对实现的IEnumerable的强制执行。
在任何情况下,多个接口继承似乎要么是多余的,要么可以用更好的替代方案来取代。
是否存在任何难以或不可能在没有多个接口继承的情况下完成的示例?

我的特殊接口应该既是可克隆的(Cloneable)又是可比较的(Comparable)吗? - amit
好的,但是我正在寻找一个真正的理由想要扩展两个不相关接口的接口。就像我上面提到的,我发现的Java示例都是“标记”,而我发现的.NET示例已经通过单一继承得到了强制执行。 - Kenneth Cochran
没有多重接口继承,任何设计都不会变得不可能或难以实现。这种实践只是促进了解耦和关注点分离,这显然不是实现某些功能所必需的(只是让它更清晰)。 - Erix
4个回答

1
如果你想让所有实现你的接口的对象都同时实现 IDisposableIComparable
例如,如果你有一组资源,你可能想要根据它们的某些属性对它们进行排序或过滤(使用 IEquatable)。这意味着所有对象都必须实现这两个接口,你可以使用一个公共接口来强制执行这一点。
IResource<T>: IDisposable, IComparable<T>, IEquatable<T>

你能举例说明什么情况下需要这样做吗?似乎可比较性是一个值,而可处理性是一个资源引用。 - Tom Hawtin - tackline

1

如果我正确理解了您描述的.NET接口继承链,那么您是错误的。这是一个容易犯的错误,因为文档让它看起来像那样。IList<T>仅明确从ICollection<T>继承;它不会ICollection<T>IEnumerable<T>IEnumerable继承。这是来自参考源代码的实际定义:

public interface IList<T> : ICollection<T>

同样适用于ICollection<T>,它只继承自IEnumerable<T>。在编译期间,接口的继承树被展平和去重。因此,继承ICollection的接口不仅继承该接口,而且还继承IEnumerable本身。
视觉示例:
IMine : ICollection<T> != IMine : (ICollection<T>:(IEnumerable<T>:IEnumerable))

相反,它看起来是这样编译的:

IMine: ICollection<T>, IEnumerable<T>, IEnumerable

这就解释了为什么你得出了你的结论。


关于多接口继承的真实世界示例,请看 IQueryableIOrderedQueryable 的示例。这些接口的实际源声明如下:

public interface IQueryable<out T> : IEnumerable<T>, IQueryable {}
public interface IOrderedQueryable<out T> : IQueryable<T>, IOrderedQueryable {}

IQueryable 需要有一个契约来指定您可以迭代每个元素,但也需要具备与简单的 IEnumerable 区分的能力。它独自一人,而不是所有的 IEnumerable 类型,可以在 IQueryProvider 和静态的 Queryable 类中使用。


另一个有趣的在System.Collections.Concurrent命名空间中。

public interface IProducerConsumerCollection<T> : IEnumerable<T>, ICollection

这很有趣,因为它继承了非泛型的ICollectionIEnumerable<T>,它们都继承自IEnumerable。当然,这不是问题,但这是另一个很好的例子。如果IProducerConsumerCollection<T>继承自其非泛型对应物ICollection<T>而不是ICollection,我相信它不会明确地继承IEnumerable<T>。它具有在ICollection中定义的大小、(旧)枚举器和同步方法行为,以及在IEnumerable<T>中定义的通用枚举行为。

这里有两个例子。我发现还有更多,但我认为这些可能已经足够了。


现在这更接近我所寻找的。IQueryable<T> 继承自两个兄弟接口,它们有一个共同的父级 (IEnumerable),但是它们彼此之间并没有继承关系。而且你是正确的,我是基于 MSDN 文档和 Redgate Reflector 来做出假设的。两者都让它看起来好像每个接口都使用多重继承直接继承了它的祖先。 - Kenneth Cochran
因此,多接口继承可以用作将设计约束强制实施到实现对象的一种方式。如果C#或Java仅支持接口的单一继承,则无法强制要求对象必须实现两个接口。 - Kenneth Cochran
好的,你的第一句话描述了接口的一般目的。接口是表明一个类以“这种方式”行为的方法。我不喜欢将接口继承看作是继承,我更喜欢认为它更接近组合,你实际上是在类中包含那些契约。你仍然可以通过单独给它们分配来强制一个类具有两个接口,但是那样你就必须对所有接口都这样做。除了多重继承之外,您可以以不同的方式完成相同的事情,但这样做会使代码难以维护且难看。 - Christopher Currens

1
你应该看一下接口隔离原则(ISP),它是SOLID原则之一。基本上,你应该创建小的接口,然后将它们组合在一起,而不是试图将它们合并在一起。为什么要强制某些类使用无用的方法呢?这通常是多重继承的原因。
至于设计是否不可能,我会说不是,因为你可以创建一个巨大的接口。然而,ISP指出了这种方法的负面影响,这可能会使维护成为潜在的困难,但从最初的设计来看,我会说你不需要多重继承来完成实现。只是看起来有点凌乱而已。

我熟悉ISP和SOLID原则,总体上我同意它们。我正在寻找设计中需要多重接口继承才能实现的例子,否则这将会很困难或不可能。 - Kenneth Cochran

0

在Java中:

Comparable通常是您希望与主类型分开的内容。

AbstractList子类型既是AbstractCollection又是List

StringBuilder既是CharSequence(读接口)又是Appendable(写接口)。我不太喜欢将读和写合并在一起 - 不可变值和tell-don't-ask接口。

显然,有(大多数是邪恶的)标记接口:SerializableCloneableRandomAccess

特别是GUI代码往往会将回调接口的完全不必要的实现合并到一个错误类中,该类通常扩展了它没有理由扩展的类。


你提供的例子都是实现了多个接口的类。我在寻找继承多个接口的接口的例子。最终我在java.nio.channels.ByteChannel中找到了一个例子,但是没有给出将ReadableByteChannel和WritableByteChannel合并成单个接口的理由。 - Kenneth Cochran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接