何时使用接口而不是抽象类,反之亦然?
这可能是一个通用的OOP问题。 我想根据用法在接口和抽象类之间进行通用的比较。
什么时候想要使用一个接口,什么时候想要使用一个抽象类 ?
我写了一篇关于这个的文章:
抽象类和接口
总结:
当我们谈论抽象类时,我们正在定义对象types的特征; 指定一个对象是什么 。
当我们谈论一个接口并定义我们承诺提供的function时,我们正在谈论的是build立一个关于对象可以做什么的合同。
抽象类可以共享状态或function。 接口只是提供状态或function的承诺。 一个好的抽象类将减less必须重写的代码量,因为它的function或状态可以被共享。 界面没有定义的信息要共享
就我个人而言,我几乎从不需要写抽象类。
大多数时候我看到抽象类被(错误)使用,这是因为抽象类的作者正在使用“模板方法”模式。
“模板方法”的问题在于它几乎总是有些重入 – “派生”类不仅知道它正在实现的基类的“抽象”方法,而且还知道基类的公共方法即使大部分时间不需要打电话给他们。
(过分简化)例子:
abstract class QuickSorter { public void Sort(object[] items) { // implementation code that somewhere along the way calls: bool less = compare(x,y); // ... more implementation code } abstract bool compare(object lhs, object rhs); }
所以在这里,这个类的作者写了一个通用的algorithm,并打算让人们通过提供自己的“钩子”来“专门化”它 – 在这种情况下,“比较”方法。
所以预期的用法是这样的:
class NameSorter : QuickSorter { public bool compare(object lhs, object rhs) { // etc. } }
这个问题是你不适当地把两个概念联系在一起:
- 比较两个项目的方法(哪个项目应该先去)
- 一个sorting项目的方法(即快速sorting与合并sorting等)
在上面的代码中,理论上,“比较”方法的作者可以重复地回到超类“sorting”方法中,尽pipe在实践中他们将永远不想或不需要这样做。
你为这种不需要的耦合付出的代价是很难改变超类,在大多数的OO语言中,在运行时不可能改变它。
另一种方法是使用“策略”devise模式:
interface IComparator { bool compare(object lhs, object rhs); } class QuickSorter { private readonly IComparator comparator; public QuickSorter(IComparator comparator) { this.comparator = comparator; } public void Sort(object[] items) { // usual code but call comparator.Compare(); } } class NameComparator : IComparator { bool compare(object lhs, object rhs) { // same code as before; } }
所以现在注意:我们所有的都是接口,以及这些接口的具体实现。 在实践中,你并不需要做任何事情来做一个高层面的OOdevise。
为了“隐藏”我们已经通过使用“QuickSort”类和“NameComparator”来实现“sorting名称”的事实,我们仍然可以在某处编写一个工厂方法:
ISorter CreateNameSorter() { return new QuickSorter(new NameComparator()); }
每当你有一个抽象类,你都可以做到这一点……即使在基类和派生类之间存在一个自然的可重入关系时,通常也要把它们明确地表示出来。
最后一个想法:我们上面所做的是通过使用“QuickSort”函数和“NameComparison”函数“编写”NameSorting函数…在function性编程语言中,这种编程风格变得更加自然,用较less的代码。
好吧,只要“亲自”就可以了 – 这里是通俗的说法(如果我错了,可以随时纠正我) – 我知道这个话题是有用的,但有一天有人会偶然发现这个话题。
抽象类允许你创build一个蓝图,并允许你另外构build(实现)你想要的所有后代拥有的属性和方法。
另一方面,一个接口只允许你声明你想要一个给定名称的属性和/或方法存在于所有实现它的类中 – 但是并不指定你应该如何实现它。 另外,一个类可以实现多个接口,但只能扩展一个Abstract类。 一个接口更像是一个高层次的build筑工具(如果你开始掌握devise模式,这个工具就会变得更加清晰) – 一个Abstract在这两个阵营中都有一席之地,可以完成一些肮脏的工作。
为什么使用一个呢? 前者允许更加具体的后代定义 – 后者允许更多的多态性 。 最后一点对最终用户/编码员来说很重要,他们可以利用这些信息来实现各种组合/形状的AP I(接口)以满足他们的需求。
我认为这对我来说是“灯泡”时刻 – 从作者的angular度来考虑接口,而不是来自任何编码人员,这些编码人员将在实现项目中增加实现或扩展 API。
我的两分钱:
一个接口基本上定义了一个契约,任何实现类都必须遵守(实现接口成员)。 它不包含任何代码。
另一方面,抽象类可以包含代码,并且可能有一些标记为抽象的方法,这是inheritance类必须实现的。
我使用抽象类的情况很less见,当我有一些默认的function时,inheritance类可能在重写,比如说一个抽象基类中,有些特定的类inheritance。
示例(一个非常基本的!):考虑一个名为Customer的基类,它具有像CalculatePayment(),CalculateRewardPoints()和一些非抽象方法(如GetName(),SavePaymentDetails())的抽象方法。
像RegularCustomer和GoldCustomer这样的专门化的类将inheritance自Customer基类并实现自己的CalculatePayment()和CalculateRewardPoints()方法逻辑,但是重新使用GetName()和SavePaymentDetails()方法。
您可以向抽象类(非抽象方法)添加更多function,而不会影响使用旧版本的子类。 而向接口添加方法会影响实现它的所有类,因为它们现在需要实现新添加的接口成员。
具有所有抽象成员的抽象类将与接口类似。
如果你有清楚的概念,什么时候做一件很简单的事情。
抽象类可以被派生,而接口可以被实现。 两者之间有一些区别。 当你派生一个Abstract类时,派生类和基类之间的关系是'是'的关系。 例如,狗是动物,羊是动物,这意味着派生类inheritance了基类的一些属性。
而对于接口的实现,关系是“可以的”。 例如,狗可以是间谍狗。 一只狗可以是一只马戏团狗。 一只狗可以是一只比赛狗。 这意味着你实现某些方法来获得一些东西。
我希望我很清楚。
如果您将java看作OOP语言,
Java 8启动时,“ 接口不提供方法实现 ”不再有效。 现在,java提供了默认方法的接口实现。
简单来说,我想用
接口:通过多个不相关的对象来实现合同。 它提供“ HAS A ”function。
抽象类:在多个相关对象之间实现相同或不同的行为。 它build立了“ 是 ”的关系。
Oracle 网站提供了interface
和abstract
类之间的关键区别。
考虑使用抽象类如果:
- 你想在几个密切相关的类中分享代码。
- 您期望扩展抽象类的类具有许多常用的方法或字段,或者需要公共访问修饰符(例如protected和private)。
- 您想要声明非静态或非最终字段。
考虑使用接口如果:
- 你期望不相关的类将实现你的接口。 例如,许多不相关的对象可以实现
Serializable
接口。 - 您想要指定特定数据types的行为,但不关心谁实现其行为。
- 你想利用types的多重inheritance。
例:
抽象类( IS关系)
读者是一个抽象的类。
BufferedReader是一个Reader
FileReader是一个Reader
FileReader
和BufferedReader
用于通用目的:读取数据,它们通过Reader
类相关。
接口( HAS Afunction)
可串行化是一个接口。
假设您的应用程序中有两个类,它们正在实现Serializable
接口
Employee implements Serializable
Game implements Serializable
这里你不能通过Employee
和Game
之间的Serializable
接口build立任何关系,这是为了不同的目的。 两者都能够对状态进行序列化,并在那里结束比较。
看看这些post:
我应该如何解释一个接口和一个抽象类的区别?
我写了一篇关于何时使用抽象类以及何时使用接口的文章。 除了“一个IS-A …和一个CAN-DO …”以外,还有更多不同之处。 对我来说,这些是jar装答案。 我提到几个什么时候使用它们的原因。 希望它有帮助。
1.如果你正在创build一些为不相关类提供通用function的东西,可以使用一个接口。
2.如果要为层次结构中密切相关的对象创build某个对象,请使用抽象类。
类只能从一个基类inheritance,所以如果你想使用抽象类为一组类提供多态性,它们都必须从这个类inheritance。 抽象类也可以提供已经实现的成员。 因此,可以确保抽象类具有一定的相同function,但不能使用接口。
以下是一些build议,可帮助您决定是使用接口还是抽象类来为组件提供多态性。
- 如果您期望创build组件的多个版本,请创build一个抽象类。 抽象类提供了一种简单而简单的方法来对组件进行版本化。 通过更新基类,所有inheritance类都会随着更改而自动更新。 另一方面,接口一旦创build就不能改变。 如果需要新版本的界面,则必须创build一个全新的界面。
- 如果您正在创build的function将在各种不同的对象中有用,请使用界面。 抽象类应主要用于密切相关的对象,而接口则最适合为不相关类提供通用function。
- 如果您正在devise小而简洁的function,请使用接口。 如果您正在devise大型function单元,请使用抽象类。
- 如果要在组件的所有实现中提供通用的实现function,请使用抽象类。 抽象类允许您部分实现您的类,而接口不包含任何成员的实现。
复制自:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx
如果这些语句适用于您的情况,请考虑使用抽象类 :
- 你想在几个密切相关的类中分享代码。
- 你期望扩展你的抽象类的类有很多通用的方法或者字段,或者需要访问修饰符而不是public(比如protected和private)。
- 您想要声明非静态或非最终字段。 这使您可以定义可访问和修改其所属对象状态的方法。
如果以下任何一种语句适用于您的情况,请考虑使用接口 :
- 你期望不相关的类将实现你的接口。 例如,Comparable和Cloneable接口是由许多不相关的类实现的。
- 您想要指定特定数据types的行为,但不关心谁实现其行为。
- 你想利用多重inheritance。
资源
答案因语言而异。 例如,在Java中,类可以实现(inheritance)多个接口,但只能从一个抽象类inheritance。 所以接口给你更多的灵活性。 但是这在C ++中是不正确的。
如果您想提供一些基本的实现,请使用抽象类。
纯粹基于inheritance,在明确定义后代,抽象关系(即动物 – >猫)和/或要求inheritance虚拟或非公有属性的情况下,尤其是共享状态(接口无法支持)。
你应该尝试和赞成合成(通过dependency injection),而不是inheritance,尽pipe如此,注意接口是契约支持unit testing,分离关注和(语言变化)多重inheritance。
接口比抽象类更好的一个有趣的地方是当你需要为一组(相关或不相关的)对象添加额外的function时。 如果你不能给它们一个基本的抽象类(例如,它们被sealed
或已经有一个父类),你可以给它们一个虚拟(空)接口,然后简单地为这个接口编写扩展方法。
我认为最简单的方法是:
共享属性=>抽象类。
共享function=>界面。
并且简洁地说…
抽象类例子:
public abstract class BaseAnimal { public int NumberOfLegs { get; set; } protected BaseAnimal(int numberOfLegs) { NumberOfLegs = numberOfLegs; } } public class Dog : BaseAnimal { public Dog() : base(4) { } } public class Human : BaseAnimal { public Human() : base(2) { } }
由于动物有一个共享属性 – 在这种情况下腿的数量 – 这是有道理的,使一个抽象的类包含这个共享的属性。 这也使我们能够编写在该属性上运行的通用代码。 例如:
public static int CountAllLegs(List<BaseAnimal> animals) { int legCount = 0; foreach (BaseAnimal animal in animals) { legCount += animal.NumberOfLegs; } return legCount; }
接口示例:
public interface IMakeSound { void MakeSound(); } public class Car : IMakeSound { public void MakeSound() => Console.WriteLine("Vroom!"); } public class Vuvuzela : IMakeSound { public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!"); }
请注意,Vuvuzelas和Cars是完全不同的东西,但它们具有共同的function:发出声音。 因此,这里的界面是有意义的。 此外,它将允许程序员将在一个通用界面下发出声音的东西分组 – 在这种情况下是IMakeSound
。 有了这个devise,你可以写下面的代码:
List<IMakeSound> soundMakers = new List<ImakeSound>(); soundMakers.Add(new Car()); soundMakers.Add(new Vuvuzela()); soundMakers.Add(new Car()); soundMakers.Add(new Vuvuzela()); soundMakers.Add(new Vuvuzela()); foreach (IMakeSound soundMaker in soundMakers) { soundMaker.MakeSound(); }
你能告诉我会输出什么吗?
最后,你可以将两者结合起来。
组合示例:
public interface IMakeSound { void MakeSound(); } public abstract class BaseAnimal : IMakeSound { public int NumberOfLegs { get; set; } protected BaseAnimal(int numberOfLegs) { NumberOfLegs = numberOfLegs; } public abstract void MakeSound(); } public class Cat : BaseAnimal { public Cat() : base(4) { } public override void MakeSound() => Console.WriteLine("Meow!"); } public class Human : BaseAnimal { public Human() : base(2) { } public override void MakeSound() => Console.WriteLine("Hello, world!"); }
在这里,我们要求所有的BaseAnimal
都发出声音,但我们还不知道它的实现。 在这种情况下,我们可以抽象接口实现并将其实现委托给它的子类。
最后一点,请记住我们如何在抽象类的例子中对不同对象的共享属性进行操作,并且在接口的例子中我们能够调用不同对象的共享function? 在这最后一个例子中,我们可以做到这一点。
在Java中,你可以inheritance一个(抽象)类来“提供”function,你可以实现许多接口来“确保”function
这可能是一个非常困难的电话,使…
我可以给出一个指针:一个对象可以实现多个接口,而一个对象只能inheritance一个基类(像C#这样的现代OO语言,我知道C ++有多重inheritance – 但不是那么皱眉?)
抽象类可以有实现。
一个接口没有实现,它只是定义了一种契约。
也可能有一些语言相关的差异:例如C#没有多重inheritance,但可以在一个类中实现多个接口。
对于我来说,我会在很多情况下使用接口。 但是在某些情况下,我更喜欢抽象类。
面向对象的类通常指的是实现。 当我想强制一些实现细节的孩子,我使用抽象类,否则我去接口。
当然,抽象类不仅在强制执行方面有用,而且在许多相关类中也有一些具体的细节。
基本的拇指规则是:对于“名词”使用抽象类和“动词”使用接口
例如: car
是一个抽象的类和drive
,我们可以把它作为一个接口。