我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
当前回答
首选接口而非抽象类
根本原因需要考虑的要点[这里已经提到了两个]是:
接口更灵活,因为一个类可以实现多个接口。由于Java没有多重继承,因此使用抽象类防止用户使用任何其他类等级制度通常,当没有默认值时,首选接口实现或状态。Java集合提供了这(地图、集合等)。抽象类的优点是允许更好的转发兼容性。一旦客户端使用了一个接口,就不能更改它;如果它们使用抽象类,您仍然可以添加行为而不使用破坏现有代码。如果兼容性是一个问题,请考虑使用抽象类。即使您有默认实现或内部状态,考虑提供一个接口及其抽象实现。这将有助于客户,但如果所需[1]。当然,这个问题已经讨论了很长时间其他地方[2,3]。
[1] 当然,它增加了更多的代码,但如果简洁是您的主要关注点,那么您可能应该首先避免使用Java!
[2] Joshua Bloch,《有效的Java》,第16-18项。
[3] http://www.codeproject.com/KB/ar...
其他回答
当我第一次开始学习面向对象编程时,我犯了一个简单的、可能也是常见的错误,即使用继承来共享公共行为——即使这种行为对对象的本质来说并不重要。
为了进一步建立这个问题中经常使用的例子,有很多东西都是可拍的——女朋友、汽车、毛毯所以我可能有一个Petable类提供了这种常见的行为,以及从中继承的各种类。
然而,可拍不是这些物体的本质的一部分。还有很多更重要的概念对他们的天性至关重要——女朋友是人,汽车是陆地车辆,猫是哺乳动物。。。
行为应该首先被分配给接口(包括类的默认接口),并且只有当它们(a)对于作为更大类的子集的一大群类来说是公共的时,才应该被提升为基类——在相同的意义上,“猫”和“人”是“哺乳动物”的子集。
问题是,当你比我一开始更了解面向对象设计之后,你通常会自动完成这项工作,甚至不用考虑它。因此,“代码到接口,而不是抽象类”这句话的真实性变得如此明显,你很难相信任何人都会不厌其烦地说出它,并开始尝试将其他含义解读到其中。
我想补充的另一点是,如果一个类是纯抽象的-没有非抽象的、非继承的成员或方法暴露给子级、父级或客户端-那么为什么它是一个类?它可以被替换,在某些情况下被接口替换,在其他情况下被Null替换。
我通常在需要之前都不会实现。与抽象类相比,我更喜欢接口,因为这提供了更多的灵活性。如果在某些继承类中有共同的行为,我会将其上移并创建一个抽象基类。我不认为两者都有必要,因为它们本质上服务于相同的目的,而且两者都有一种糟糕的代码气味(imho),解决方案被过度设计了。
资料来源:http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C#是一种奇妙的语言,在过去的14年中不断成熟和发展。这对我们开发人员来说很好,因为一种成熟的语言为我们提供了大量的语言特性,供我们使用。
然而,权力大了,责任大了。其中一些功能可能被误用,或者有时很难理解为什么您会选择使用一个功能而不是另一个功能。多年来,我看到许多开发人员都在纠结于一个特性,那就是什么时候选择使用接口或者选择使用抽象类。两者都有各自的优点和缺点,以及使用它们的正确时间和地点。但我们如何决定???
两者都提供了类型之间公共功能的重用。最明显的区别是,接口不提供其功能的实现,而抽象类允许您实现一些“基本”或“默认”行为,然后可以在必要时使用类派生类型“覆盖”此默认行为。
这一切都很好,并且提供了代码的重用,并且遵循了软件开发的DRY(不要重复自己)原则。当你有一种“是”的关系时,抽象类很好用。
例如:金毛寻回犬是一种狗。贵宾犬也是。它们都会吠叫,就像所有的狗一样。然而,您可能需要指出,贵宾犬公园与“默认”狗叫有明显不同。因此,您可以执行以下操作:
public abstract class Dog
{
public virtual void Bark()
{
Console.WriteLine("Base Class implementation of Bark");
}
}
public class GoldenRetriever : Dog
{
// the Bark method is inherited from the Dog class
}
public class Poodle : Dog
{
// here we are overriding the base functionality of Bark with our new implementation
// specific to the Poodle class
public override void Bark()
{
Console.WriteLine("Poodle's implementation of Bark");
}
}
// Add a list of dogs to a collection and call the bark method.
void Main()
{
var poodle = new Poodle();
var goldenRetriever = new GoldenRetriever();
var dogs = new List<Dog>();
dogs.Add(poodle);
dogs.Add(goldenRetriever);
foreach (var dog in dogs)
{
dog.Bark();
}
}
// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark
//
正如您所看到的,这将是一种保持代码干燥的好方法,并允许在任何类型只能依赖默认Bark而不是特殊情况实现时调用基类实现。GoldenRetriever、Boxer、Lab等类都可以免费继承“默认”(低音类)Bark,因为它们实现了Dog抽象类。
但我相信你已经知道了。
您来到这里是因为您想了解为什么您可能希望选择一个接口而不是抽象类,反之亦然。嗯,您可能希望选择接口而不是抽象类的一个原因是,当您没有或希望阻止默认实现时。这通常是因为实现接口的类型与“是”关系无关。事实上,除了每种类型“能够”或“有能力”做某事或拥有某事这一事实之外,它们根本不必是相关的。
那到底是什么意思?嗯,举个例子:人不是鸭子……鸭子也不是人。很明显。然而,鸭子和人类都有游泳的“能力”(考虑到人类在一年级通过了游泳课程:)。此外,由于鸭子不是人,反之亦然,这不是“是一种”关系,而是“能够”关系,我们可以使用一个界面来说明:
// Create ISwimable interface
public interface ISwimable
{
public void Swim();
}
// Have Human implement ISwimable Interface
public class Human : ISwimable
public void Swim()
{
//Human's implementation of Swim
Console.WriteLine("I'm a human swimming!");
}
// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
public void Swim()
{
// Duck's implementation of Swim
Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
}
}
//Now they can both be used in places where you just need an object that has the ability "to swim"
public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
somethingThatCanSwim.Swim();
}
public void Main()
{
var human = new Human();
var duck = new Duck();
var listOfThingsThatCanSwim = new List<ISwimable>();
listOfThingsThatCanSwim.Add(duck);
listOfThingsThatCanSwim.Add(human);
foreach (var something in listOfThingsThatCanSwim)
{
ShowHowYouSwim(something);
}
}
// So at runtime the correct implementation of something.Swim() will be called
// Output:
// Quack! Quack! I'm a Duck swimming!
// I'm a human swimming!
使用上面的代码这样的接口将允许您将对象传递给“能够”执行某些操作的方法。代码不在乎它是如何做到的……它只知道它可以对该对象调用Swim方法,并且该对象将根据其类型知道在运行时采取的行为。
再次,这有助于代码保持干燥,这样您就不必编写多个调用对象的方法来预成型相同的核心函数(ShowHowHumanSwims(人类)、ShowHowDuckSwims(鸭子)等)
在这里使用接口允许调用方法不必担心行为是什么类型或如何实现的。它只是知道,给定接口,每个对象都必须实现Swim方法,因此在自己的代码中调用它是安全的,并允许在自己的类中处理Swim方法的行为。
摘要:
因此,我的主要经验法则是,当你想为一个类层次结构或/和你正在处理的类或类型实现一个“默认”功能时,使用一个抽象类。
另一方面,如果你没有“是一种”关系,但类型共享“能力”来做某事或拥有某事(例如,鸭子“不是”人类“。然而,鸭子和人类共享“游泳能力”),则使用界面。
抽象类和接口之间需要注意的另一个区别是,一个类可以实现一对多接口,但一个类只能从一个抽象类(或任何类)继承。是的,您可以嵌套类并具有继承层次结构(许多程序都这样做,也应该这样做),但不能在一个派生类定义中继承两个类(这一规则适用于C#。在其他一些语言中,您可以这样做,通常是因为这些语言中缺少接口)。
还请记住,使用接口时要遵守接口隔离原则(ISP)。ISP表示,不应强迫任何客户端依赖它不使用的方法。因此,接口应该专注于特定的任务,并且通常非常小(例如IDisposable、IComparable)。
另一个提示是,如果您正在开发小而简洁的功能,请使用接口。如果要设计大型功能单元,请使用抽象类。
希望这能为一些人扫清障碍!
此外,如果你能想出任何更好的例子或想指出什么,请在下面的评论中这样做!
接口有一个明显的优点,即对类来说有点“热交换”。将一个类从一个父类更改为另一个父级通常会导致大量工作,但通常可以删除和更改接口,而不会对实现类产生很大影响。这在您有几个“可能”希望类实现的窄行为集的情况下尤其有用。
这在我的领域尤其适用:游戏编程。基类可能会因继承对象“可能”需要的大量行为而变得臃肿。通过接口,可以轻松地向对象添加或删除不同的行为。例如,如果我为想要反映伤害的对象创建了“IDamageEffects”界面,那么我可以很容易地将其应用于各种游戏对象,并在以后很容易地改变主意。假设我设计了一个要用于“静态”装饰对象的初始类,我最初决定它们是不可破坏的。稍后,我可能会决定,如果它们可以爆炸,那会更有趣,所以我修改了类以实现“IDamageEffects”接口。这比切换基类或创建新的对象层次结构要容易得多。
接口应较小。真的很小。如果你真的要分解你的对象,那么你的接口可能只包含一些非常具体的方法和财产。
抽象类是快捷方式。PetBase的所有衍生产品都有你可以编写一次代码并完成的东西吗?如果是的话,那么是抽象类的时候了。
抽象类也是有限的。虽然它们为您提供了生成子对象的绝佳捷径,但任何给定对象只能实现一个抽象类。很多时候,我发现这是抽象类的局限性,这也是我使用大量接口的原因。
抽象类可以包含多个接口。您的PetBase抽象类可以实现IPet(宠物有主人)和IDigestion(宠物吃,或者至少它们应该吃)。然而,PetBase可能不会实施IMammal,因为并非所有宠物都是哺乳动物,也并非所有哺乳动物都是宠物。您可以添加一个扩展PetBase的MammalPetBase并添加IMammal。FishBase可以有PetBase并添加IFish。IFish将使用ISwim和IUnderwaterBreaker作为接口。
是的,对于简单的示例来说,我的示例非常复杂,但这是接口和抽象类如何协同工作的一部分。