这可能是一个通用的OOP问题。我想在接口和抽象类的使用基础上做一个通用的比较。
什么时候需要使用接口,什么时候需要使用抽象类?
这可能是一个通用的OOP问题。我想在接口和抽象类的使用基础上做一个通用的比较。
什么时候需要使用接口,什么时候需要使用抽象类?
当前回答
好吧,我自己刚刚“grokup”了这个——这是外行的术语(如果我错了,请随意纠正我)——我知道这个话题太老了,但其他人可能有一天会偶然发现它……
抽象类允许你创建一个蓝图,并允许你额外构造(实现)属性和方法,你希望它的所有后代都拥有。
另一方面,接口只允许您声明希望在实现它的所有类中存在具有给定名称的属性和/或方法——但不指定应该如何实现它。同样,一个类可以实现许多接口,但只能扩展一个抽象类。界面更像是一种高级架构工具(如果你开始掌握设计模式,这一点就会变得更清楚)——抽象则同时涉足这两个阵营,也可以执行一些繁琐的工作。
为什么使用其中一种而不是另一种呢?前者允许对后代进行更具体的定义,而后者允许更大的多态性。最后一点对最终用户/编码器很重要,他们可以利用这些信息以各种组合/形状来实现A.P.I(界面),以满足他们的需求。
我认为这对我来说是一个“顿悟”的时刻——少从作者的角度来考虑接口,多从后面加入到项目中实现或扩展API的编码器的角度来考虑接口。
其他回答
什么时候做什么是一件非常简单的事情,如果你有清晰的概念在你的脑海里。
抽象类可以是派生类,而接口可以是实现类。这两者之间有一些区别。当派生抽象类时,派生类和基类之间的关系是“is a”关系。例如,狗是动物,羊是动物,这意味着派生类从基类继承了一些属性。
而对于接口的实现,关系是“可以是”。例:狗可以是间谍犬。狗可以是马戏团的狗。狗可以是比赛犬。这意味着你要实现特定的方法来获取某些东西。
我希望我讲清楚了。
在Java中,您可以从一个(抽象)类继承来“提供”功能,并且可以实现许多接口来“确保”功能
如果您想提供一些基本实现,请使用抽象类。
就我个人而言,我几乎从不需要编写抽象类。
大多数时候,我看到抽象类被(错误地)使用,这是因为抽象类的作者使用了“模板方法”模式。
“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的编码器的角度来考虑接口。