一个接口是否应该继承另一个接口?

90

我无法在这个问题上找到答案,只想确保它是一个正确的编码标准。 我有一个接口A,被许多不同的类使用,而且不想让接口A改变。 我遇到了一个新需求,需要许多实现接口A的类使用枚举,但并非所有类都需要此枚举。 我不希望没有需要此新枚举的类实现此新功能。 因此,我创建了接口B,包含我需要添加的新枚举。 然后我让接口B继承了接口A,这是我的担忧,一个接口是否可以继承另一个接口? 为了继续更改,我将需要新枚举的类更改为实现接口B而不是接口A,因为它被接口B继承。 我考虑在需要它们的类中同时实现两个接口,但我正在整个代码中使用接口,并且希望只使用一个接口来查看类而不是两个。

希望这足够清楚(可能太长了),如果有人能给我一些建议,无论是我做得对还是做得错,请告诉我。

谢谢!

6个回答

92

接口继承是一种很好的工具,但只有当接口B真正可以替代接口A时,才应该使用它,而不仅仅是为了聚合松散相关的行为。

很难确定它是否适用于您的特定情况,但原则上使用这种做法并没有错。你经常在一流的API中看到它。举一个常见的例子,来自.NET框架:

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

2
我最初建议应该应用Liskov替换原则。回想起来,那并不是很相关。大多数LSP的要求(维护不变量、前置条件和后置条件的限制)实际上只适用于具体的实现,而不是接口。话虽如此,可替换性的一般原则仍应指导接口继承决策。 - Jeff Sternal
里氏替换原则:确保接口B能够完全替代接口A非常重要。否则,你最终会得到一些你不想实现的功能。这会导致你不需要的额外代码,从而使软件不稳定。 - Chad Crowe
我发现在替换一个现有依赖项需要第二个实现时,使用外观模式效果很好。与其更改原始类和所有测试以需要工厂,外观处理使用工厂并返回所需结果。我使用标记接口将两个不同的接口注入到工厂中,外观将使用该工厂。只有工厂知道“标记”接口。虽然不是理想的解决方案,但我比更改现有类更喜欢它。 - DanCaveman

29

考虑接口是否应该逻辑配对,如果你认为它们彼此配合良好,则绝对可以使用继承。

让我们来看一个例子;

public interface IScanner
{
    void Scan();
}

public interface IPrinter
{
    void Print();
}

打印机和扫描仪通常是分开的,具有各自的功能,但这两个设备经常被组合在同一设备中;

public interface IPhotocopier : IScanner, IPrinter
{
    void Copy();
}

IPhotocopier继承IScanner和IPrinter是有道理的,因为这使得复印机除了其主要角色之外,还可以用作扫描仪或打印机(它包含了这些功能)。

现在让我们再看一个接口;

public interface IBlender
{
    void Blend();
}

让IBlender被之前的任何接口继承都没有意义(你会叫它什么?IBlendingScanner?)。

如果您无法为新接口命名,请考虑在此情况下不使用继承。

继承某些接口(例如IDisposable)是一个坏主意,因为这会强制所有实现您的新接口的实现实现dispose模式,即使它们没有任何可处理的资源。


15

严格来说,接口之间并不会继承。当你创建一个从IBar继承的IFoo时,实际上是在表明实现IFoo的任何类也必须实现IBar

interface IBar
{
    void DoBar();
}

interface IFoo : IBar
{
    void DoFoo();
}
在此示例中,IFoo 接口没有 DoBar() 方法。大多数情况下,这种区别并不重要,但是当在接口而不是类上使用反射时,它可能会给你带来麻烦。

7
尽管你所描述的行为(在Phil Haack最近的专栏文章中详细描述,http://haacked.com/archive/2009/11/10/interface-inheritance-esoterica.aspx)已经说明得非常好,但我认为在C#中说“接口不从彼此继承”过于混淆视听。因为C#参考文档甚至使用了这种术语,例如:“一个接口可以从一个或多个基础接口继承。”(http://msdn.microsoft.com/en-us/library/87d83y5b%28VS.80%29.aspx)我更愿意说,在C#中,接口继承在反映继承成员时的行为与类继承不同。 - Jeff Sternal
1
好的,我想这取决于“继承”的定义方式。当然,无论是类还是接口,C#语法都是相同的。但是,如果您认为继承包括父级成员,则在讨论接口时,您的心理模型与现实不符。 - Joel Mueller
谢谢指出这一点。在处理接口反射时,我没有意识到它是这样工作的。 - KevinVictor

6

当然可以有一个接口的继承树,甚至可以使用接口进行“多重继承”。是否应该这样做取决于涉及到的接口。如果确实是B接口对A接口的扩展或细化,那么继承就有意义了。但是,如果新枚举与由接口A表示的概念基本无关,则我会将它们作为两个单独的接口,并让需要实现两个接口的类来实现它们。


5

我认为数据库始终提供了一种很好的方式来展示接口,因此,在考虑一个接口是否应该继承另一个接口时,请参考以下内容:

IMySqlDatabase : IDatabase
MySqlDatabase : IMySqlDatabase

IMsSqlDatabase : IDatabase
MsSqlDatabase : IMsSqlDatabase

一个MySqlDatabase是一个IMySqlDatabase,而一个IMySqlDatabase是一个IDatabase。

现在如果您需要对IDatabase接口进行更改,则其子类(具体的数据库类)可以获得好处,但您不必扩展MySQL和MsSQL(或甚至更多DBMS的接口)。同时,在中间人(IMsSqlDatabase)中,您仍然可以拥有MySQL或Oracle DB不支持的接口功能。


2

我认为这正是正确的方法,我没有看到任何问题。


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