接口与基类的区别

836

我应该在什么时候使用接口,什么时候使用基类?

如果我不想定义方法的基本实现,是否总是应该使用接口?

如果我有一个Dog和Cat类,为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)等功能提供接口,因为这些功能可以针对每个宠物进行设置,但我不明白为什么要为通用Pet使用哪种方式。


11
我认为你应该考虑的一个要点是——接口可能会带来一些限制,直到非常晚的阶段你才会意识到这些限制。例如,在 .NET 中,您无法序列化接口成员变量,因此,如果您有一个名为 Zoo 的类和一个 IAnimals 数组成员变量,您将无法序列化 Zoo(这意味着编写 WebServices 或其他需要序列化的东西将会很麻烦)。 - synhershko
2
这个问题可能有助于理解接口的概念。https://dev59.com/cWoy5IYBdhLWcg3wdt4L - gprathour
我只是好奇。我在 CLR via C# 中遇到了以下摘录:“我倾向于使用接口技术而不是基类型技术,因为基类型技术不允许开发人员选择最适合特定情况的基类型。” 我无法理解摘录中的意思。我们可以创建几个基类型,并为其中任何一个创建派生类型,因此开发人员可以选择基类型。请问有人能解释一下吗?我相信这可能是这个问题的一部分。还是我应该发布另一个关于具体摘录的问题? - qqqqqqq
38个回答

1

列出你的对象必须拥有、具备或执行的事项清单,以及你的对象可以(或可能)拥有、具备或执行的事项清单。其中,必须表示你的基础类型,而可以表示你的接口。

例如,你的PetBase 必须呼吸,而你的IPet 可能会表演技巧。

通过对问题域的分析,有助于定义精确的层次结构。


1
一个基类的继承者应该具有“是一个”关系。接口代表了一个“实现了”的关系。因此,只有在您的继承者将保持“是一个”关系时才使用基类。

0

感谢Jon Limjap的回答,但我想为接口和抽象基类的概念添加一些解释。

接口类型 vs. 抽象基类

摘自Pro C# 5.0 and the .NET 4.5 Framework书籍。

接口类型看起来非常类似于抽象基类。请记住,当一个类被标记为抽象时,它可以定义任意数量的抽象成员,以向所有派生类型提供多态接口。然而,即使一个类定义了一组抽象成员,它也可以自由地定义任意数量的构造函数、字段数据、带实现的非抽象成员等等。相反,接口仅包含抽象成员定义。抽象父类建立的多态接口存在一个主要限制,即仅有派生类型支持抽象父类定义的成员。但是,在更大的软件系统中,很常见开发多个类层次结构,除了System.Object之外没有共同的父级。鉴于抽象基类中的抽象成员仅适用于派生类型,我们无法配置不同层次结构中的类型以支持相同的多态接口。例如,假设您已定义了以下抽象类:
public abstract class CloneableType
{
// Only derived types can support this
// "polymorphic interface." Classes in other
// hierarchies have no access to this abstract
// member.
   public abstract object Clone();
}

根据这个定义,只有扩展CloneableType的成员才能支持Clone()方法。如果您创建了一组不扩展此基类的类,那么您无法获得此多态接口。此外,您可能还记得,C#不支持类的多重继承。因此,如果您想创建一个既是Car又是CloneableType的MiniVan,您将无法实现:

// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}

正如你所猜测的那样,接口类型能够解决问题。一旦定义了一个接口,任何类或结构都可以在任何层次结构、任何命名空间或任何程序集中(使用任何.NET编程语言)实现它。可以看出,接口是高度多态的。考虑一下标准的.NET接口ICloneable,它定义在System命名空间中。该接口定义了一个名为Clone()的方法:

public interface ICloneable
{
object Clone();
}

0
除了那些提到IPet/PetBase实现的评论之外,还有一些情况下提供访问器辅助类可能非常有价值。
IPet/PetBase风格假定您有多个实现,因此增加了PetBase的价值,因为它简化了实现。然而,如果您具有相反或两者混合的情况,即您有多个客户端,则提供一个类来帮助使用接口可以通过使接口更易于使用来降低成本。

0

运用你自己的判断力并且要聪明。不要总是听从别人(比如我)所说的话。你会听到“优先使用接口而不是抽象类”,但这实际上取决于情况。它取决于这个类是什么。

在上述有对象层次结构的情况下,接口是一个好主意。接口有助于处理这些对象的集合,并且在实现与层次结构中任何对象一起工作的服务时也很有帮助。你只需定义与层次结构中的对象一起工作的契约即可。

另一方面,当你实现一堆共享通用功能的服务时,你可以将共同功能分离成一个完全独立的类,或者将其移动到一个公共基类中并使其抽象化,以便没有人可以实例化基类。

还要考虑如何随着时间的推移支持你的抽象化。接口是固定的:你发布一个接口作为任何类型都可以实现的一组功能的契约。基类可以随着时间的推移进行扩展。这些扩展成为每个派生类的一部分。


0

接口在类方面具有明显的优势,可以进行“热插拔”。将一个类从一个父类更改为另一个父类通常会导致很多工作量,但接口通常可以轻松移除和更改,而对实现类的影响不大。这在你有几种行为集可能需要一个类来实现的情况下尤其有用。

这在我的领域——游戏编程中特别适用。基础类可能会因为继承对象可能需要的大量行为而变得臃肿。使用接口可以轻松快速地将不同的行为添加或移除到对象中。例如,如果我为想要反射伤害的对象创建一个“IDamageEffects”接口,则我可以轻松应用于各种游戏对象,并随时更改主意。比如说,我设计了一种初始类来用于“静态”的装饰性物品,最初我决定它们是不可摧毁的。后来,我可能会决定如果他们能够爆炸会更加有趣,所以我可以改变类来实现“IDamageEffects”接口,这比更换基础类或创建新的对象层次结构要容易得多。


0

继承还有其他优点,例如变量能够容纳父类或继承类的对象(无需将其声明为通用类型,如“Object”)。

例如,在.NET WinForms中,大多数UI组件都派生自System.Windows.Forms.Control,因此声明为该类型的变量可以“容纳”几乎任何UI元素 - 按钮、ListView等等。当然,您无法访问项目的所有属性或方法,但您将拥有所有基本内容 - 这可能很有用。


你的例子并不支持这个概念。你可以用接口做同样的事情,你不一定非要有一个基类。 - Kilhoffer

0

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