这可能是一个通用的OOP问题。我想在接口和抽象类的使用基础上做一个通用的比较。
什么时候需要使用接口,什么时候需要使用抽象类?
这可能是一个通用的OOP问题。我想在接口和抽象类的使用基础上做一个通用的比较。
什么时候需要使用接口,什么时候需要使用抽象类?
当前回答
就我个人而言,我几乎从不需要编写抽象类。
大多数时候,我看到抽象类被(错误地)使用,这是因为抽象类的作者使用了“模板方法”模式。
“Template方法”的问题在于它几乎总是某种程度上是可重入的——“派生”类不仅知道它正在实现的基类的“抽象”方法,还知道基类的公共方法,即使大多数时候它不需要调用它们。
(过于简化的)例子:
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);
}
因此,在这里,该类的作者编写了一个泛型算法,并打算通过提供自己的“钩子”(在本例中是一个“比较”方法)来“专门化”它,以供人们使用。
所以预期的用法是这样的:
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
这样做的问题在于,你将两个概念过度地耦合在了一起:
比较两个项目的一种方法(哪个项目应该放在前面) 排序项目的方法(即快速排序vs归并排序等)
在上面的代码中,从理论上讲,“compare”方法的作者可以重新调用超类“Sort”方法…即使在实践中,他们永远不会想要或需要这样做。
为这种不必要的耦合付出的代价是,很难更改超类,而且在大多数OO语言中,不可能在运行时更改它。
另一种方法是使用“策略”设计模式:
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;
}
}
现在请注意:我们所拥有的只是接口,以及这些接口的具体实现。在实践中,您实际上不需要任何其他东西来进行高级OO设计。
为了“隐藏”我们已经通过使用“QuickSort”类和“NameComparator”实现了“名称排序”的事实,我们仍然可以在某个地方写一个工厂方法:
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
任何时候你有一个抽象类,你都可以这样做…即使基类和派生类之间存在自然的可重入关系,将它们显式化通常也是值得的。
最后一个想法:我们上面所做的一切都是通过使用“QuickSort”函数和“NameComparison”函数来“组合”一个“NameSorting”函数……在函数式编程语言中,这种编程风格变得更加自然,代码更少。
其他回答
抽象类可以具有共享的状态或功能。接口只是提供状态或功能的承诺。一个好的抽象类可以减少必须重写的代码量,因为它的功能或状态可以共享。接口没有可共享的已定义信息
好吧,我自己刚刚“grokup”了这个——这是外行的术语(如果我错了,请随意纠正我)——我知道这个话题太老了,但其他人可能有一天会偶然发现它……
抽象类允许你创建一个蓝图,并允许你额外构造(实现)属性和方法,你希望它的所有后代都拥有。
另一方面,接口只允许您声明希望在实现它的所有类中存在具有给定名称的属性和/或方法——但不指定应该如何实现它。同样,一个类可以实现许多接口,但只能扩展一个抽象类。界面更像是一种高级架构工具(如果你开始掌握设计模式,这一点就会变得更清楚)——抽象则同时涉足这两个阵营,也可以执行一些繁琐的工作。
为什么使用其中一种而不是另一种呢?前者允许对后代进行更具体的定义,而后者允许更大的多态性。最后一点对最终用户/编码器很重要,他们可以利用这些信息以各种组合/形状来实现A.P.I(界面),以满足他们的需求。
我认为这对我来说是一个“顿悟”的时刻——少从作者的角度来考虑接口,多从后面加入到项目中实现或扩展API的编码器的角度来考虑接口。
车辆包括汽车、坦克、飞机、手推车等。
抽象类Vehicle可以有子类,如car、tank、plane、cart等。
public abstract Vehicle {...}
public Car extends Vehicle {...}
public Tank extends Vehicle {...}
那么,什么是可移动的?几乎一切! 然后
石头,蛋糕,汽车,行星,星系,甚至你都是可移动的!
它们中的大多数也是可观察的!
什么是可吃的?这是一款名为《美味星球》的游戏。 然后
石头,汽车,行星,星系,甚至时间!
public interface Movable {...}
public interface Observable {...}
public interface Eatable {...}
public class Stone implements Movable, Eatable, Observable {...}
public class Time implements Eatable, Observable {...}
public class Stupidity implements Observable {...}
终于!
public class ChocolateCar extends Vehicle implements Eatable {...}
这是一个很难打的电话。
我可以给出一个提示:一个对象可以实现许多接口,而一个对象只能继承一个基类(在像c#这样的现代OO语言中,我知道c++有多个继承-但这不是不受欢迎吗?)
如果你认为java是面向对象语言,
“接口不提供方法实现”在Java 8启动后不再有效。现在java为默认方法提供了接口实现。
简单来说,我想用
接口:通过多个不相关的对象实现一个契约。它提供了“HAS A”功能。
抽象类:在多个相关对象之间实现相同或不同的行为。它建立了“IS A”关系。
Oracle网站提供了接口和抽象类之间的关键区别。
考虑使用抽象类,如果:
您希望在几个密切相关的类之间共享代码。 您希望扩展抽象类的类具有许多公共方法或字段,或者需要除public以外的访问修饰符(例如protected和private)。 您希望声明非静态或非final字段。
考虑使用接口,如果:
您希望不相关的类实现您的接口。例如,许多不相关的对象都可以实现Serializable接口。 您希望指定特定数据类型的行为,但不关心由谁实现其行为。 您希望利用类型的多重继承。
例子:
抽象类(IS一个关系)
Reader是一个抽象类。
BufferedReader是一个Reader
FileReader是一个阅读器
FileReader和BufferedReader用于一个共同的目的:读取数据,它们通过Reader类相互关联。
接口(具有功能)
Serializable是一个接口。
假设您的应用程序中有两个实现Serializable接口的类
Employee实现Serializable
游戏实现Serializable
在这里,你不能通过Serializable接口在员工和游戏之间建立任何关系,这是为了不同的目的。两者都能够序列化状态,比较到此结束。
看看这些帖子:
我应该如何解释接口和抽象类之间的区别?