我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
当前回答
在这篇Java World文章中对其进行了很好的解释。
就我个人而言,我倾向于使用接口来定义接口,即系统设计中指定如何访问某些内容的部分。
我会有一个类实现一个或多个接口,这并不罕见。
抽象类是我用来做其他事情的基础。
以下是上述文章JavaWorld.com文章的摘录,作者Tony Sintes,2001年4月20日
接口与抽象类选择接口和抽象类不是一个非此即彼的命题。如果你需要改变你的设计,让它成为一个界面。然而,您可能有提供一些默认行为的抽象类。抽象类是应用程序框架中的优秀候选者。抽象类允许您定义一些行为;它们迫使您的子类提供其他子类。例如,如果您有一个应用程序框架,抽象类可以提供默认服务,如事件和消息处理。这些服务允许应用程序插入应用程序框架。但是,有些特定于应用程序的功能只有您的应用程序才能执行。此类功能可能包括启动和关闭任务,这些任务通常依赖于应用程序。因此,抽象基类可以声明抽象的关闭和启动方法,而不是试图定义该行为本身。基类知道它需要这些方法,但抽象类让您的类承认它不知道如何执行这些操作;它只知道它必须发起行动。当需要启动时,抽象类可以调用启动方法。当基类调用此方法时,Java调用子类定义的方法。许多开发人员忘记了定义抽象方法的类也可以调用该方法。抽象类是创建计划继承层次结构的极好方法。对于类层次结构中的非叶类,它们也是一个很好的选择。类与接口
有人说你应该用接口来定义所有的类,但我认为这个建议有点极端。当我看到设计中的某些内容会频繁更改时,我会使用接口。
例如,Strategy模式允许您在程序中交换新的算法和过程,而不改变使用它们的对象。媒体播放器可能知道如何播放CD、MP3和wav文件。当然,您不希望将这些回放算法硬编码到播放器中;这将使添加像AVI这样的新格式变得困难。此外,您的代码将充斥着无用的case语句。为了增加伤害,您需要在每次添加新算法时更新这些案例陈述。总而言之,这不是一种非常面向对象的编程方式。使用Strategy模式,您可以简单地将算法封装在对象后面。如果这样做,您可以随时提供新的媒体插件。让我们将插件类称为MediaStrategy。该对象将有一个方法:playStream(流)。所以为了添加一个新的算法,我们只需扩展我们的算法类。现在,当程序遇到新的媒体类型时,它只是将流的播放委托给我们的媒体策略。当然,您需要一些管道来正确实例化所需的算法策略。这是一个使用界面的好地方。我们使用了Strategy模式,它清楚地表明设计中的一个位置将发生变化。因此,您应该将策略定义为一个接口。当您希望某个对象具有某种类型时,通常应该支持接口而不是继承;在本例中为MediaStrategy。依赖类型标识的继承是危险的;它将您锁定到特定的继承层次结构中。Java不允许多重继承,因此您不能扩展能够为您提供有用实现或更多类型标识的内容。
其他回答
另一个要记住的选项是使用“has-a”关系,也就是“以”或“组合的方式实现”。有时这是一种比使用“is-a”继承更干净、更灵活的结构方式。
从逻辑上说,狗和猫都“拥有”一只宠物可能不太合理,但它避免了常见的多重继承陷阱:
public class Pet
{
void Bathe();
void Train(Trick t);
}
public class Dog
{
private Pet pet;
public void Bathe() { pet.Bathe(); }
public void Train(Trick t) { pet.Train(t); }
}
public class Cat
{
private Pet pet;
public void Bathe() { pet.Bathe(); }
public void Train(Trick t) { pet.Train(t); }
}
是的,这个例子表明,在以这种方式做事时存在大量的代码重复和缺乏优雅。但我们也应该意识到,这有助于保持狗和猫与宠物类的分离(因为狗和猫不能访问宠物的私人成员),并为狗和猫从其他东西(可能是哺乳动物类)继承留下了空间。
当不需要私人访问并且您不需要使用通用的宠物参考/指针来参考狗和猫时,合成是最好的。接口为您提供了通用的引用功能,可以帮助减少代码的冗长,但当组织不好时,它们也会使事情变得模糊。当您需要私人成员访问时,继承是有用的,在使用它时,您将致力于将您的狗和猫类与您的宠物类高度耦合,这是一个巨大的成本。
在继承、组合和接口之间,没有一种方法总是正确的,考虑如何协调地使用这三种选项是有帮助的。在这三个选项中,继承通常是最不常用的选项。
胡安,
我喜欢将接口视为一种描述类的方式。一个特定的犬类,比如约克郡梗,可能是母犬类的后代,但它也执行IFurry、IStubby和IYippieDog。所以类定义了类是什么,但接口告诉了我们关于它的事情。
这样做的好处是,例如,它允许我收集所有的IYippieDog,并将它们放入我的海洋收藏。因此,现在我可以跨越一组特定的对象,找到符合我所关注的标准的对象,而不必过于仔细地检查类。
我发现接口确实应该定义类的公共行为的子集。如果它为实现的所有类定义了所有公共行为,那么它通常不需要存在。他们没有告诉我任何有用的东西。
但这种想法与每个类都应该有一个接口的想法背道而驰,您应该编写接口代码。这很好,但最终会有很多一对一的类接口,这会让事情变得混乱。我知道这样做并不需要花费任何费用,现在你可以轻松地交换东西。然而,我发现我很少这样做。大多数时候,我只是在原地修改现有的类,如果该类的公共接口需要更改,我会遇到完全相同的问题,但我现在必须在两个地方更改它。
所以,如果你像我一样思考,你肯定会说《猫和狗》是IPettable的。这是一个符合两者的特征。
但另一个问题是,它们是否应该有相同的基类?问题是,它们是否需要被广泛地视为同一事物。当然,它们都是动物,但这是否符合我们将如何一起使用它们。
假设我想收集所有动物类并将它们放在我的方舟容器中。
或者它们需要是哺乳动物吗?也许我们需要某种跨动物挤奶工厂?
他们甚至需要联系在一起吗?仅仅知道它们都是IPettable就足够了吗?
当我真的只需要一个类时,我常常会感到想要派生出一个完整的类层次结构。我做这件事是为了期待有一天我可能会需要它,但通常我从来不会这样做。即使我做了,我通常也会发现我必须做很多事情来修复它。这是因为我创建的第一个类不是狗,我没有那么幸运,而是鸭嘴兽。现在,我的整个类层次结构都基于这个奇怪的案例,我有很多浪费的代码。
你可能还会发现,并不是所有的猫都是IPettable的(比如那个无毛的猫)。现在您可以将该接口移动到所有适合的派生类。你会发现,突然之间,猫不再是PettableBase的产物,这是一个小得多的变化。
通过def,接口提供了一个与其他代码通信的层。默认情况下,类的所有公共财产和方法都实现隐式接口。我们也可以将接口定义为一个角色,当任何类都需要扮演这个角色时,它必须实现它,根据实现它的类,给它不同的实现形式。oops的两个概念!!!
现代风格是定义IPet和PetBase。
该接口的优点是其他代码可以使用它,而与其他可执行代码没有任何联系。完全“干净”。界面也可以混合。
但是基类对于简单的实现和通用实用程序很有用。因此,还提供一个抽象基类,以节省时间和代码。
当我第一次开始学习面向对象编程时,我犯了一个简单的、可能也是常见的错误,即使用继承来共享公共行为——即使这种行为对对象的本质来说并不重要。
为了进一步建立这个问题中经常使用的例子,有很多东西都是可拍的——女朋友、汽车、毛毯所以我可能有一个Petable类提供了这种常见的行为,以及从中继承的各种类。
然而,可拍不是这些物体的本质的一部分。还有很多更重要的概念对他们的天性至关重要——女朋友是人,汽车是陆地车辆,猫是哺乳动物。。。
行为应该首先被分配给接口(包括类的默认接口),并且只有当它们(a)对于作为更大类的子集的一大群类来说是公共的时,才应该被提升为基类——在相同的意义上,“猫”和“人”是“哺乳动物”的子集。
问题是,当你比我一开始更了解面向对象设计之后,你通常会自动完成这项工作,甚至不用考虑它。因此,“代码到接口,而不是抽象类”这句话的真实性变得如此明显,你很难相信任何人都会不厌其烦地说出它,并开始尝试将其他含义解读到其中。
我想补充的另一点是,如果一个类是纯抽象的-没有非抽象的、非继承的成员或方法暴露给子级、父级或客户端-那么为什么它是一个类?它可以被替换,在某些情况下被接口替换,在其他情况下被Null替换。