接口更灵活,因为类可以实现多个接口 . 由于Java没有多重继承,因此使用抽象类可以防止用户使用任何其他类层次结构 . In general, prefer interfaces when there are no default implementations or state. Java集合提供了很好的例子(Map,Set等) .
抽象类具有允许更好的向前兼容性的优点 . 客户端使用界面后,您无法更改它;如果他们使用抽象类,您仍然可以在不破坏现有代码的情况下添加行为 . If compatibility is a concern, consider using abstract classes.
即使您有默认实现或内部状态, consider offering an interface and an abstract implementation of it . 这将有助于客户,但如果需要,仍然允许他们更大的自由[1] . 当然,这个问题已在其他地方详细讨论[2,3] .
1. You have a general interface (eg IPet)
2. You have a implementation that is less general (eg Mammal)
3. You have many concrete members (eg Cat, Dog, Ape)
抽象类定义具体类的默认共享属性,但强制执行接口 . 例如:
public interface IPet{
public boolean hasHair();
public boolean walksUprights();
public boolean hasNipples();
}
现在,由于所有的哺乳动物都有头发和乳头(AFAIK,我不是动物学家),我们可以把它变成抽象基类
public abstract class Mammal() implements IPet{
@override
public walksUpright(){
throw new NotSupportedException("Walks Upright not implemented");
}
@override
public hasNipples(){return true}
@override
public hasHair(){return true}
然后具体的课程只是定义他们直立行走 .
public class Ape extends Mammal(){
@override
public walksUpright(return true)
}
public class Catextends Mammal(){
@override
public walksUpright(return false)
}
一个重要的区别是您只能继承 one 基类,但您可以实现 many 接口 . 所以你只想使用一个基类,如果你绝对肯定你赢了't need to also inherit a different base class. Additionally, if you find your interface is getting large then you should start looking to break it up into a few logical pieces that define independent functionality, since there'没有规则你的类不能全部实现它们(或者你可以定义一个不同的接口,只是继承它们以对它们进行分组) .
关于何时使用接口与抽象基类和仅仅类的观点将根据您正在开发的内容以及您正在开发的语言而大相径庭 . 接口通常仅与静态类型语言(如Java或C#)相关联,但是动态类型语言也可以有接口和抽象基类 . 例如,在Python中,一个Class(声明它是一个接口)和一个对象(它是一个Class的实例)之间的区别是明确的,并且被称为 provide 这个接口 . 在动态语言中,两个对象都是同一个类的实例,可以声明它们提供完全 different 接口 . 在Python中,这仅适用于对象属性,而方法是共享状态在一个类的所有对象之间 . 但是在Ruby中,对象可以有每个实例的方法,所以它有任何明确的方式来声明接口) .
在动态语言中,通常隐式地假定对象的接口,或者通过内省对象并询问它提供了什么方法(Look Before You Leap),或者最好通过简单地尝试在对象上使用所需的接口并捕获异常(如果对象)没有提供该接口(更容易请求宽恕而不是权限) . 这可能导致“误报”,其中两个接口具有相同的方法名称但在语义上不同,但是权衡是您的代码更灵活,因为您不需要预先指定预先考虑所有可能的用途你的代码
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); }
}
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
//
// 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!
30 回答
通过def,接口提供了与其他代码通信的层 . 默认情况下,类的所有公共属性和方法都实现隐式接口 . 我们还可以将接口定义为角色,当任何类需要扮演该角色时,它必须实现它,根据实现它的类,为其提供不同的实现形式 . 因此,当你谈论接口时,你谈论的是多态性,当你谈论基类时,你谈论的是继承 . oops的两个概念!!!
接口和基类代表两种不同形式的关系 .
Inheritance (基类)表示"is-a"关系 . 例如 . 一只狗或一只猫"is-a"宠物 . 此关系始终表示类的(单个) purpose (与"single responsibility principle"一起) .
另一方面, Interfaces 代表一个类的 additional features . 我称之为"is"关系,就像“
Foo
是一次性的”,因此C#中的IDisposable
接口 .关于C#,在某种意义上,接口和抽象类可以互换 . 但是,不同之处在于:i)接口无法实现代码; ii)因此,接口无法进一步调用堆栈到子类; iii)只有抽象类可以在类上继承,而多个接口可以在类上实现 .
另外请记住,不要在OO(see blog)中被扫除并始终根据所需行为建模对象,如果您正在设计一个应用程序,其中您需要的唯一行为是动物的通用名称和物种,那么您只需要一个class动物的名称属性,而不是世界上每种可能动物的数百万个类 .
在Java World article解释得很好
我个人倾向于使用接口来定义接口 - 即系统设计的一部分,用于指定应该如何访问某些内容 .
我将有一个实现1个或更多接口的类并不罕见 .
抽象类我用作其他东西的基础 .
以下是上述文章JavaWorld.com article, author Tony Sintes, 04/20/01的摘录
让我们以你的Dog和Cat类为例,让我们用C#来说明:
狗和猫都是动物,特别是四足动物的哺乳动物(动物过于笼统) . 让我们假设你有两个抽象类Mammal:
这个基类可能有默认方法,例如:
Feed
伙伴
所有这些都是在两个物种之间具有或多或少相同实施的行为 . 要定义它,您将拥有:
现在让我们假设还有其他哺乳动物,我们通常会在动物园看到它们:
这仍然有效,因为功能的核心
Feed()
和Mate()
仍然是相同的 .然而,长颈鹿,犀牛和河马并不是你可以养宠物的动物 . 这就是界面有用的地方:
上述 Contract 的实施在猫与狗之间是不一样的;将他们的实现放在一个抽象类中继承将是一个坏主意 .
您的狗和猫定义现在应该如下所示:
从理论上讲,您可以从更高的基类覆盖它们,但实质上,接口允许您只需将所需的内容添加到类中而无需继承 .
因此,因为您通常只能从一个抽象类继承(在大多数静态类型的OO语言中,例外包括C)但是能够实现多个接口,它允许您严格按照要求构造对象 .
这是接口和基类的基本和简单定义:
基类=对象继承 .
Interface =功能继承 .
干杯
这是非常特定于.NET的,但是框架设计指南书认为,通用类在不断发展的框架中提供了更大的灵活性 . 一旦接口出厂,您就没有机会在不破坏使用该接口的代码的情况下进行更改 . 但是,对于类,您可以修改它,而不是破坏链接到它的代码 . 只要您做出正确的修改(包括添加新功能),您就可以扩展和改进代码 .
Krzysztof Cwalina在第81页说:
话虽这么说肯定有接口的地方 . 作为一般指导,如果没有其他任何内容作为实现接口的方式的示例,则始终提供接口的抽象基类实现 . 在最好的情况下,基类将节省大量的工作 .
Prefer interfaces over abstract classes
基本原理,要考虑的要点[这里已经提到过两个]是:
接口更灵活,因为类可以实现多个接口 . 由于Java没有多重继承,因此使用抽象类可以防止用户使用任何其他类层次结构 . In general, prefer interfaces when there are no default implementations or state. Java集合提供了很好的例子(Map,Set等) .
抽象类具有允许更好的向前兼容性的优点 . 客户端使用界面后,您无法更改它;如果他们使用抽象类,您仍然可以在不破坏现有代码的情况下添加行为 . If compatibility is a concern, consider using abstract classes.
即使您有默认实现或内部状态, consider offering an interface and an abstract implementation of it . 这将有助于客户,但如果需要,仍然允许他们更大的自由[1] .
当然,这个问题已在其他地方详细讨论[2,3] .
[1]当然,它增加了更多代码,但如果简洁是您最关心的问题,那么您可能应该首先避免使用Java!
[2] Joshua Bloch,Effective Java,第16-18项 .
[3] http://www.codeproject.com/KB/ar ......
我建议尽可能使用合成而不是继承 . 使用接口但使用成员对象进行基本实现 . 这样,您可以定义一个工厂,构造您的对象以某种方式运行 . 如果要更改行为,则创建一个新的工厂方法(或抽象工厂),以创建不同类型的子对象 .
在某些情况下,如果在辅助对象中定义了所有可变行为,您可能会发现主要对象根本不需要接口 .
因此,您最终可能会使用Pet,而不是IPet或PetBase它有一个IFurBehavior参数 . IFurBehavior参数由PetFactory的CreateDog()方法设置 . 这个参数是为shed()方法调用的 .
如果你这样做,你会发现你的代码更灵活,你的大多数简单对象都处理非常基本的系统行为 .
即使在多重继承语言中,我也推荐这种模式 .
这取决于您的要求 . 如果IPet足够简单,我宁愿实现它 . 否则,如果PetBase实现了大量功能,您不想复制,那么请使用它 .
实现基类的缺点是需要
override
(或new
)现有方法 . 这使得它们成为虚拟方法,这意味着您必须小心如何使用对象实例 .最后,.NET的单一继承杀了我 . 一个简单的例子:假设您正在创建用户控件,因此您继承了
UserControl
. 但是,现在你被锁定也继承了PetBase
. 这会强制您重新组织,例如创建一个PetBase
类成员 .好吧,Josh Bloch在Effective Java 2d说:
首选接口而不是抽象类
一些要点:
另一方面,接口很难发展 . 如果向接口添加方法,它将破坏它的所有实现 .
PS:买这本书 . 它更加详细 .
Submain .NET编码指南中详细解释了基于接口的基类的情况:
当我第一次开始学习面向对象编程时,我犯了一个简单而且可能是使用继承来共享共同行为的常见错误 - 即使这种行为对于对象的本质并不重要 .
为了进一步 Build 一个在这个特定问题中使用的例子,有许多可以用的东西 - 女朋友,汽车,模糊毛毯...... - 所以我可能有一个Petable类提供了这种常见的行为,并且各种类继承从中 .
但是,容易接受不属于任何这些对象的性质 . 那里是非常重要的概念,对他们的本性至关重要 - 女朋友是一个人,汽车是陆地车辆,猫是哺乳动物......
应首先将行为分配给接口(包括类的默认接口),并且只有当它们(a)对于作为较大类的子集的大类类是通用的时才提升为基类 - 与“猫”和“人”是“哺乳动物”的子集 .
问题是,在您完全了解面向对象设计之后,您通常会自动执行此操作,而不必考虑它 . 所以“代码接口而不是抽象类”这一陈述的明显事实变得如此明显,你很难相信任何人都会费心去说 - 并开始尝试将其他含义读入其中 .
我要添加的另一件事是,如果一个类纯粹是抽象的 - 没有非抽象的,非继承的成员或方法暴露给子,父或客户端 - 那么为什么它是一个类?它可以在某些情况下由接口替换,在其他情况下由Null替换 .
除非您知道它的含义,否则不要使用基类,并且它适用于这种情况 . 如果适用,请使用它,否则,使用接口 . 但请注意关于小接口的答案 .
公共继承在OOD中被过度使用,并且比大多数开发人员意识到或愿意接受的表达要多得多 . 见Liskov Substitutablity Principle
简而言之,如果A“是”B,那么对于它暴露的每种方法,A只需要不超过B并且不超过B .
接口应该很小 . 真的很小 . 如果你真的打破了你的对象,那么你的接口可能只包含一些非常具体的方法和属性 .
抽象类是快捷方式 . 有没有PetBase的所有衍生品共享你可以编码一次并完成的东西?如果是,那么是抽象课的时间 .
抽象类也是有限的 . 虽然它们为您提供了生成子对象的快捷方式,但任何给定对象都只能实现一个抽象类 . 很多时候,我发现这是Abstract类的限制,这就是我使用大量接口的原因 .
抽象类可能包含多个接口 . 你的PetBase抽象类可以实现IPet(宠物拥有者)和IDigestion(宠物吃,或至少他们应该) . 然而,PetBase可能不会实施IMammal,因为并非所有宠物都是哺乳动物,并非所有哺乳动物都是宠物 . 您可以添加一个扩展PetBase并添加IMammal的MammalPetBase . FishBase可以拥有PetBase并添加IFish . IFish将ISwim和IUnderwaterBreather作为接口 .
是的,我的例子对于这个简单的例子来说过于复杂,但这是关于接口和抽象类如何协同工作的一部分 .
我有一个粗略的经验法则
Functionality: 可能在所有部分都有所不同:界面 .
Data, and functionality, parts will be mostly the same, parts different: 抽象类 .
Data, and functionality, actually working, if extended only with slight changes: 普通(具体)类
具有最终修饰符的 Data and functionality, no changes planned: 普通(具体)类 .
Data, and maybe functionality: read-only: 枚举成员 .
这是非常粗略和准备好的,并没有严格定义,但是有一个来自接口的频谱,其中所有内容都要更改为枚举,其中所有内容都修复有点像只读文件 .
胡安,
我喜欢将接口视为表征类的一种方式 . 一个特定的狗品种,比如YorkshireTerrier,可能是父狗类的后代,但它也实现了IFurry,IStubby和IYippieDog . 所以这个类定义了类是什么,但接口告诉我们关于它的事情 .
这样做的好处是它允许我收集所有的IYippieDog并将它们扔进我的Ocean系列 . 因此,现在我可以跨越一组特定的对象,找到符合我正在查看的标准的对象,而无需过于仔细地检查课程 .
我发现接口确实应该定义一个类的公共行为的子集 . 如果它定义了所有实现的类的所有公共行为,那么它通常不需要存在 . 他们没有告诉我任何有用的东西 .
这个想法虽然与每个类都应该有一个接口并且你应该编写接口的想法背道而驰 . 这很好,但是你最终会遇到很多一对一的类接口,这让事情变得混乱 . 据我所知,这个想法并不需要花费任何费用,现在你可以轻松地进出东西 . 但是,我发现我很少这样做 . 大多数时候我只是修改现有的类,并且如果该类的公共接口需要更改,我总是会遇到完全相同的问题,除了我现在必须在两个地方更改它 .
因此,如果你像我一样思考,你肯定会说Cat和Dog是IPettable . 这是一种与它们相匹配的特征 .
另一部分是他们应该有相同的基类吗?问题是他们需要被广泛地视为同一件事 . 当然它们都是动物,但这适合我们将要一起使用它们 .
假设我想收集所有Animal类并将它们放入我的Ark容器中 .
还是他们需要成为哺乳动物?也许我们需要某种跨动物挤奶工厂?
他们甚至需要联系在一起吗?只知道它们都是IPettable就足够了吗?
当我真正需要一个 class 时,我常常觉得有必要得出一个完整的类层次结构 . 我希望总有一天我可能需要它,通常我永远不会这样做 . 即使我这样做,我也常常发现我必须做很多事来解决它 . 那是因为我创造的第一堂课不是狗,我不是那么幸运,而是鸭嘴兽 . 现在我的整个类层次结构基于奇怪的情况,我有很多浪费的代码 .
您可能还会发现,并非所有Cats都是IPettable(就像那个无毛的那样) . 现在,您可以将该接口移动到适合的所有派生类 . 你会发现一个不那么突破的变化,突然之间的Cats不再来自PettableBase .
@Joel:某些语言(例如C)允许多重继承 .
我发现Interface> Abstract> Concrete的模式在以下用例中起作用:
抽象类定义具体类的默认共享属性,但强制执行接口 . 例如:
现在,由于所有的哺乳动物都有头发和乳头(AFAIK,我不是动物学家),我们可以把它变成抽象基类
然后具体的课程只是定义他们直立行走 .
当有许多具体类时,这种设计很好,并且您不希望仅仅为了编程接口而维护样板 . 如果在接口中添加了新方法,则会破坏所有生成的类,因此您仍然可以获得接口方法的优势 .
In this case, the abstract could just as well be concrete; however, the abstract designation helps to emphasize that this pattern is being employed.
在我需要之前,我通常都不会实施 . 我更喜欢接口而不是抽象类,因为它提供了更多的灵活性 . 如果某些继承类中存在共同行为,我会将其移动并创建一个抽象基类 . 我没有看到两者的需要,因为它们本质上服务于相同的目的,并且两者都是一个糟糕的代码味道(imho),解决方案已被过度设计 .
一个重要的区别是您只能继承 one 基类,但您可以实现 many 接口 . 所以你只想使用一个基类,如果你绝对肯定你赢了't need to also inherit a different base class. Additionally, if you find your interface is getting large then you should start looking to break it up into a few logical pieces that define independent functionality, since there'没有规则你的类不能全部实现它们(或者你可以定义一个不同的接口,只是继承它们以对它们进行分组) .
以前关于使用抽象类进行常规实现的评论肯定是标记的 . 我还没有看到的一个好处是,使用接口可以更容易地实现模拟对象以进行单元测试 . 如Jason Cohen所描述的那样定义IPet和PetBase使您可以轻松地模拟不同的数据条件,而无需物理数据库的开销(直到您决定测试真实数据库的时间) .
从概念上讲,接口用于正式和半正式地定义对象将提供的一组方法 . 形式上是指一组方法名称和签名,半正式地表示与这些方法相关的人类可读文档 . 接口只是API的描述(毕竟,API代表Application Programmer Interface ),它们可以't contain any implementation, and it'不能使用或运行接口 . 他们只明确说明你应该如何与一个对象进行交互 .
类提供了一个实现,他们可以声明它们实现了零个,一个或多个接口 . 如果要继承Class,则约定是使用“Base”作为Class名称的前缀 .
基类和抽象基类(ABC)之间存在区别 . ABCs将接口和实现混合在一起 . 计算机编程之外的摘要意味着“摘要”,即“抽象==接口” . 然后,抽象基类可以描述接口,以及要继承的空,部分或完整实现 .
关于何时使用接口与抽象基类和仅仅类的观点将根据您正在开发的内容以及您正在开发的语言而大相径庭 . 接口通常仅与静态类型语言(如Java或C#)相关联,但是动态类型语言也可以有接口和抽象基类 . 例如,在Python中,一个Class(声明它是一个接口)和一个对象(它是一个Class的实例)之间的区别是明确的,并且被称为 provide 这个接口 . 在动态语言中,两个对象都是同一个类的实例,可以声明它们提供完全 different 接口 . 在Python中,这仅适用于对象属性,而方法是共享状态在一个类的所有对象之间 . 但是在Ruby中,对象可以有每个实例的方法,所以它有任何明确的方式来声明接口) .
在动态语言中,通常隐式地假定对象的接口,或者通过内省对象并询问它提供了什么方法(Look Before You Leap),或者最好通过简单地尝试在对象上使用所需的接口并捕获异常(如果对象)没有提供该接口(更容易请求宽恕而不是权限) . 这可能导致“误报”,其中两个接口具有相同的方法名称但在语义上不同,但是权衡是您的代码更灵活,因为您不需要预先指定预先考虑所有可能的用途你的代码
要记住的另一个选择是使用“has-a”关系,又称“以”或“组合”的形式实现 . 有时这是一种更简洁,更灵活的结构化方法,而不是使用“is-a”继承 .
从逻辑上讲,狗和猫都“拥有”宠物可能没有多大意义,但它避免了常见的多重继承陷阱:
是的,这个例子表明,这样做有很多代码重复和缺乏优雅 . 但是人们也应该意识到,这有助于让狗和猫脱离宠物类(因为狗和猫无法接触宠物的私人成员),并且它为狗和猫留下了继承其他东西的空间 - - 可能是哺乳动物班 .
如果不需要私人访问,并且您不需要使用通用Pet引用/指针来引用Dog和Cat,则组合是首选 . 接口为您提供了通用的引用功能,可以帮助减少代码的冗长,但是当它们组织不良时,它们也可能会混淆 . 当您需要私有成员访问权限时,继承非常有用,并且在使用它时,您承诺将您的Dog和Cat类高度耦合到您的Pet类,这需要付出高昂的代价 .
在继承,组合和接口之间,没有一种方法总是正确的,并且有助于考虑如何协调使用所有三个选项 . 在这三者中,继承通常是应该最少使用的选项 .
通常,您应该支持接口而不是抽象类 . 使用抽象类的一个原因是,如果您在具体类之间有共同的实现 . 当然,你仍然应该声明一个接口(IPet)并有一个抽象类(PetBase)实现该接口 . 使用小的,不同的接口,你可以使用倍数来进一步提高灵活性 . 接口允许跨越边界的类型的最大灵活性和可移植性 . 跨越边界传递引用时,始终传递接口而不是具体类型 . 这允许接收端确定具体实现并提供最大的灵活性 . 当以TDD / BDD方式编程时,这是绝对正确的 .
“四人帮”在他们的书中指出“因为继承将子类暴露给其父类实现的细节,所以通常会说'继承会破坏封装” . 我相信这是真的 .
接口
定义2个模块之间的 Contract . 不能有任何实施 .
大多数语言允许您实现多个接口
修改界面是一个重大变化 . 所有实现都需要重新编译/修改 .
所有成员都是公开的 . 实现必须实现所有成员 .
接口有助于去耦 . 您可以使用模拟框架来模拟接口后面的任何内容
接口通常表示一种行为
接口实现彼此分离/隔离
基类
允许您添加一些 default 实现,您可以通过派生免费获得
除C外,您只能从一个类派生 . 即使可以来自多个 class ,通常也是一个坏主意 .
更改基类相对容易 . 派生不需要做任何特别的事情
基类可以声明可以通过派生访问的受保护和公共函数
Abstract基类不能像接口那样容易被模拟
基类通常表示类型层次结构(IS A)
类派生可能依赖于某些基本行为(具有对父实现的复杂知识) . 如果你改变一个人的基础实现并打破其他人,事情就会变得混乱 .
Source :http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C#是一种很好的语言,在过去的14年里已经成熟和发展 . 这对我们的开发人员来说非常棒,因为成熟的语言为我们提供了大量的语言功能 .
但是,有很多权力变得很重要 . 其中一些功能可能被滥用,或者有时很难理解为什么你会选择使用一个功能而不是另一个功能 . 多年来,我看到很多开发人员都在努力解决的一个功能是何时选择使用接口或选择使用抽象类 . 两者都有优点和缺点以及正确的时间和地点 . 但是我们如何决定???
两者都提供了类型之间的共同功能的重用 . 最明显的区别是接口不提供其功能的实现,而抽象类允许您实现一些“基本”或“默认”行为,然后能够在必要时使用类派生类型“覆盖”此默认行为 .
这一切都很好,并提供了很好的代码重用,并坚持软件开发的DRY(不要重复自己)原则 . 当你有“是一种”关系时,抽象类很有用 .
例如:金毛猎犬“是一种”狗 . 狮子狗也是如此 . 它们都可以像所有狗一样吠叫 . 但是,您可能想要声明贵宾犬公园与“默认”狗皮大不相同 . 因此,您可以按如下方式实现:
正如您所看到的,这将是保持代码DRY并允许在任何类型可以仅依赖于默认Bark而不是特殊情况实现时调用基类实现的好方法 . 像GoldenRetriever,Boxer,Lab这样的类都可以免费继承“默认”(低音类)Bark,因为它们实现了Dog抽象类 .
但我相信你已经知道了 .
你在这里是因为你想了解为什么你可能想要在抽象类上选择一个接口,反之亦然 . 您可能想要在抽象类上选择接口的一个原因是您没有或想要阻止默认实现 . 这通常是因为实现接口的类型与“是”关系无关 . 实际上,除了每种类型“能够”或“有能力”做某事或有某事之外,它们根本不需要相关 .
那到底是什么意思呢?嗯,例如:人类不是鸭子......鸭子不是人类 . 很明显 . 然而,鸭子和人类都有“游泳能力”(考虑到人类通过了他在一年级的游泳课:)) . 此外,由于鸭子不是人类,反之亦然,这不是“是一种”关系,而是“能够”关系,我们可以使用界面来说明:
使用上面代码之类的接口将允许您将对象传递给“能够”执行某些操作的方法 . 代码并不关心它是如何做到的......它只知道它可以在该对象上调用Swim方法,并且该对象将根据其类型知道在运行时采取的行为 .
再次,这可以帮助您的代码保持DRY,这样您就不必编写多个调用该对象的方法来执行相同的核心功能(ShowHowHumanSwims(human),ShowHowDuckSwims(duck)等)
在这里使用接口允许调用方法不必担心行为的实现类型或方式 . 它只知道给定接口,每个对象都必须实现Swim方法,因此可以安全地在自己的代码中调用它,并允许在自己的类中处理Swim方法的行为 .
摘要:
因此,我的主要经验法则是,当您想要为类层次结构实现“默认”功能时使用抽象类或/和您正在使用的类或类型共享“是一种”关系(例如,poodle“是一个“狗的类型 .
另一方面,当你没有“是一种”关系时,使用一个界面,但是有一些类型可以分享“能力”来做某事或有某些东西(例如鸭子“不是”人类 . 但是,鸭子和人类分享“游泳的能力” .
抽象类和接口之间需要注意的另一个区别是,类可以实现一个到多个接口,但类只能从一个抽象类(或任何类)继承 . 是的,你可以嵌套类并拥有一个继承层次结构(许多程序都应该这样做),但你不能在一个派生类定义中继承两个类(这个规则适用于C# . 在其他一些语言中你可以这样做,通常只是因为这些语言缺乏接口) .
还要记住使用接口遵守接口隔离原则(ISP) . ISP声明不应该强迫任何客户端依赖它不使用的方法 . 因此,接口应该专注于特定任务,并且通常非常小(例如IDisposable,IComparable) .
另一个提示是,如果您正在开发小巧,简洁的功能,请使用接口 . 如果要设计大型功能单元,请使用抽象类 .
希望这能为某些人解决问题!
此外,如果你能想到任何更好的例子或想要指出一些东西请在下面的评论中这样做!
现代风格是定义IPet和PetBase .
该接口的优点是其他代码可以使用它而与其他可执行代码无任何关系 . 完全“干净” . 接口也可以混合使用 .
但是基类对于简单实现和常用实用程序很有用 . 因此,提供一个抽象基类以节省时间和代码 .
基类的继承者应该具有“是一种”关系 . 接口表示“实现”关系 . 因此,只有在继承者维护关系时才使用基类 .