为什么接口首选抽象类?

我最近参加了一个采访,他们问我“为什么接口比抽象类更受欢迎?

我试着给出几个答案,如:

  • 我们只能得到一个Extendsfunction
  • 他们是100%摘要
  • 实现不是硬编码的

他们问我使用任何你使用的JDBC API。 “他们为什么是接口?”

我能得到更好的答案吗?

那个面试问题反映了提问人的某种信念。 我相信这个人是错的,所以你可以走两个方向之一。

  1. 给他们他们想要的答案。
  2. 恭敬地不同意。

他们想要的答案,以及其他海报已经突出了那些非常好。 多接口inheritance,inheritance强迫类做实现select,接口可以更容易地改变。

但是,如果您在意见分歧中提出了一个引人注目的(而且是正确的)观点,那么面试官可能会记下来。 首先,突出关于接口的积极的事情,这是必须的。 其次,我认为接口在许多情况下都更好,但也会导致代码重复,这是一个负面的事情。 如果你有大量的子类,这些子类将在很大程度上做相同的实现,再加上额外的function,那么你可能需要一个抽象类。 它允许你有许多类似的对象细粒度的细节,而只有接口,你必须有许多不同的对象与几乎重复的代码。

接口有很多用途,有一个令人信服的理由相信他们是“更好的”。 但是,你应该始终使用正确的工具来完成这个工作,这意味着你不能把抽象类去掉。

一般来说,这决不是盲目追随的“规则”,最灵活的安排是:

interface abstract class concrete class 1 concrete class 2 

界面是有几个原因:

  • 一个已经扩展了某些东西的现有类可以实现这个接口(假设你可以控制现有类的代码)
  • 现有的类可以是子类,而子类可以实现接口(假设现有的类是可子类的)

这意味着您可以使用预先存在的类(或者只是必须从其他类扩展的类),并让它们与您的代码一起工作。

抽象类在那里为具体类提供所有常见的位。 抽象类是从编写新类或修改要扩展它的类(假定它们从java.lang.Objectinheritance)中扩展而来的。

你应该总是(除非你有一个非常好的理由)声明variables(实例,类,本地和方法参数)作为接口。

你只能inheritance一枪。 如果你创build一个抽象类而不是一个接口,inheritance你的类的人不能也inheritance一个不同的抽象类。

您可以实现多个接口,但只能从一个类inheritance

抽象类

1.不能独立于派生类来实例化。 抽象类的构造函数只能由派生类调用。

2.定义基类必须实现的抽象成员签名。

3.比接口更具扩展性,不会破坏任何版本的兼容性。 对于抽象类,可以添加所有派生类可以inheritance的其他非抽象成员。

4.可以包含存储在字段中的数据。

5.允许具有实现的(虚拟)成员,因此,为派生类提供成员的默认实现。

6.从抽象类派生使用一个子类的唯一的基类选项。

接口

1.不能被实例化。

2.接口的所有成员都在基类中实现。 在实施课堂上只能实施一些成员是不可能的。

扩展与其他成员的接口打破了版本的兼容性。

4.不能存储任何数据。 字段只能在派生类中指定。 解决方法是定义属性,但没有实现。

5.所有成员都是自动虚拟的,不能包含任何实现。

虽然没有默认的实现可以出现,但实现接口的类可以继续从另一个派生。

正如devinb和其他人所说的 ,这听起来像面试官显示他们不接受你有效答案的无知。

但是,提到JDBC可能是一个暗示。 在这种情况下,也许他们要求客户端编码而不是类接口的好处。

因此,与类别devise相关的“你只得到一种inheritance”等完全有效的答案,他们可能正在寻找一个更类似于“将客户从具体实现中分离出来”的答案。

抽象类有许多潜在的缺陷。 例如,如果重写一个方法,除非明确调用它,否则不会调用super()方法。 这可能会导致执行不当的重写类的问题。 另外,在使用inheritance时, equals()也会有潜在的问题。

当你想共享一个实现时,使用接口可以鼓励使用合成。 组成通常是更好地重用其他对象的方法,因为它不那么脆弱。 inheritance很容易被滥用或用于错误的目的。

定义一个接口是一个非常安全的方法来定义一个对象应该如何行动,而不会冒着可能延伸到另一个类,抽象或不抽象的脆性。

另外,正如你所提到的,你一次只能扩展一个类,但是你可以根据需要实现多个接口。

抽象类是在inheritance实现时使用的,在inheritance规范时使用接口。 JDBC标准声明“一个连接必须这样做”。 这是规范。

当你使用抽象类时,你创build了子类和基类之间的耦合。 这种耦合有时会使代码真的难以改变,特别是随着子类数量的增加。 接口没有这个问题。

你也只有一个inheritance,所以你应该确保你使用它的正确原因。

“为什么接口比抽象类更受欢迎?”

其他职位在界面和抽象类之间的差异方面做了很多工作,所以我不会重复这些想法。

但是在面试问题中,更好的问题是“ 什么时候接口应该优先于抽象类?” (反之亦然)。

和大多数程序devise一样,它们是有原因的,像面试问题那样绝对的陈述往往会错过。 这让我想起了所有关于C语言中的goto语句的陈述。“你永远不要使用goto–它会暴露出糟糕的编程技巧”。 但是, goto总是有其适当的用途。

尊敬地不同意上面的大多数海报(对不起!如果你想要:-)请把我改下来)


首先,“只有一个超级”的答案是跛脚的。 任何在采访中给我答案的人都会很快反驳说:“C ++在Java和C ++有多个超类之前就已经存在了,你为什么认为James Gosling只允许有一个Java超类呢?

理解你的答案背后的哲学,否则你是烤面包(至less如果我采访你。)


其次,接口与抽象类相比具有多种优势,特别是在devise接口时。 最大的一个是没有对方法的调用者施加特定的类结构。 没有什么比尝试使用需要特定类结构的方法调用更糟糕了。 这是痛苦和尴尬。 使用一个接口, 任何东西都可以以最低的期望传递给方法。

例:

 public void foo(Hashtable bar); 

 public void foo(Map bar); 

对于前者,调用者将始终将他们现有的数据结构,并将其关入一个新的Hashtable。


第三,接口允许具体类实现者的公共方法是“私有”的。 如果该方法没有在接口中声明,那么该方法不能被使用该方法的业务的类使用(或滥用)。 这让我想点4 ….


第四,接口代表了实现类和调用者之间的最小契约。 这个最小的合同确切地指定了具体的实现者如何使用,而不是更多。 调用类不允许使用接口“合约”未指定的其他方法。 正在使用的接口名称也体现了开发人员对如何使用该对象的期望。 如果开发人员通过了一个

 public interface FragmentVisitor { public void visit(Node node); } 

开发人员知道他们可以调用的唯一方法是访问方法。 他们不会被那些不应该混淆的具体课程中明亮shiny的方法分散注意力。


最后,抽象类有许多方法,这些方法实际上只存在于要使用的子类中。 所以抽象类对于外部开发者来说往往看起来有点像一团糟,对于外部代码打算使用哪些方法没有任何指导。

当然,有些这样的方法可以得到保护。 然而,可悲的保护方法也可以在同一个包中的其他类中看到。 如果一个抽象类的方法实现了一个接口,该方法必须是公共的。

然而,使用接口的时候,所有这些看着抽象的超类或具体类的内脏都被安全地藏起来了。


是的,我知道开发者当然可以使用一些“特殊的”知识将对象投射到另一个更广泛的接口或具体的类本身。 但这样的演员违反了预期的合同,开发商应该用三文鱼打耳光。

如果他们认为X比YI好就不会担心找工作,那么我就不喜欢为那些强迫我devise一个devise的人工作,因为他们被告知界面是最好的。 这两种情况都是好的,否则为什么语言select添加抽象类? 当然,语言devise师比我聪明。

这是“多重inheritance”的问题。 我们可以通过另一个类“扩展”不多于一个abstarct类,但是在Interfaces中,我们可以在单个类中“实现”多个接口。 所以,尽pipeJava并没有提供一般的多重inheritance,但是通过使用接口,我们可以在其中包含多重inheritance属性。

希望这可以帮助!!!

interface是一个纯粹抽象的类的更清洁的方式。 你可以知道实现没有潜入(当然你可能想在某些维护阶段这样做,这使得接口不好)。 就是这样。 客户端代码几乎没有区别。

JDBC是一个非常糟糕的例子。 询问任何试图实现接口并维护JDK版本之间的代码的人。 JAX-WS更糟,在更新版本中添加了方法。

有技术上的差异,比如能够繁殖“inheritance”接口。 这往往是困惑的devise的结果。 在极less数情况下,具有与接口层次结构不同的实现层次结构可能会很有用。

对于接口的缺点,编译器无法find一些不可能的cast / instanceof

有上面没有提到的一个原因。

您可以使用java.lang.reflect.Proxy轻松地修饰任何接口,从而允许您在运行时将自定义代码添加到给定接口中的任何方法。 这是非常强大的。

有关教程,请参阅http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html

接口不能代替抽象类

比较喜欢

接口:通过多个不相关的对象来实现合同

抽象类:在多个相关对象之间实现相同或不同的行为


关于接口和抽象类的用例,请参考这个相关的SE问题

界面与抽象类(通用OO)

用例:

如果必须使用Template_method模式,则无法使用接口来实现。 应select抽象类来实现它。

如果你必须实现许多不受版权保护的对象的function,那么抽象类就不能达到目的,你必须select接口

你可以实现多个接口,但特别是用c#你不能有多个inheritance

因为接口不会强迫你进入一些inheritance层次结构。

当你只需要某个对象实现某些方法,但不关心它的血统时,就可以定义接口。 所以有人可以扩展一个现有的类来实现一个接口,而不会影响该类以前存在的行为。

这就是为什么JDBC是所有接口的原因。 您并不关心在JDBC实现中使用了哪些类,只需要任何JDBC实现具有相同的预期行为。 在内部,Oracle JDBC驱动程序可能与PostgreSQL驱动程序非常不同,但这与您无关。 一个人可能不得不从数据库开发者已经拥有的一些内部类inheritance,而另一个类可能是从头开始完全开发的,但是这对你并不重要,只要他们都实现相同的接口,以便你可以与一个或者其他不知道内部工作。

那么,我build议这个问题本身应该改写。 接口主要是一个类获得的契约,契约本身的实现将会有所不同。 一个抽象类通常会包含一些默认的逻辑,其子类将会添加更多的逻辑。 我会说,这个问题的答案依赖于钻石问题。 Java防止多重inheritance来避免它。 ( http://en.wikipedia.org/wiki/Diamond_problem )。

他们问我使用任何你使用的JDBC API。 “他们为什么是接口?”

我对这个具体问题的回答是:

SUN不知道如何实现它们,或者执行什么。 它由服务提供商/数据库供应商把他们的逻辑落实。

JDBCdevise与Bridge模式有关系,该模式表示“将抽象从其实现中分离出来,以使两者可以独立变化”。

这意味着JDBC api的接口层次结构可以不考虑jdbc供应商提供或使用的实现层次结构。

抽象类提供了一种定义行为模板的方法,用户插入细节。

一个很好的例子是Java 6的SwingWorker 。 它定义了一个框架在后台执行某些操作,要求用户为实际任务定义doInBackground()

我扩展了这个类,以便它自动创build一个popup进度条。 我重写done()来控制这个popup窗口的处理,但是随后提供了一个新的覆盖点,允许用户select定义进度条消失后发生的事情。

 public abstract class ProgressiveSwingWorker<T, V> extends SwingWorker<T, V> { private JFrame progress; public ProgressiveSwingWorker(final String title, final String label) { SwingUtilities.invokeLater(new Runnable() { @SuppressWarnings("serial") @Override public void run() { progress = new JFrame() {{ setLayout(new MigLayout("","[grow]")); setTitle(title); add(new JLabel(label)); JProgressBar bar = new JProgressBar(); bar.setIndeterminate(true); add(bar); pack(); setLocationRelativeTo(null); setVisible(true); }}; } }); } /** * This method has been marked final to secure disposing of the progress dialog. Any behavior * intended for this should be put in afterProgressBarDisposed. */ @Override protected final void done() { progress.dispose(); try { afterProgressBarDisposed(get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } protected void afterProgressBarDisposed(T results) { } } 

用户仍然需要提供doInBackground()的实现。 但是,它们也可能具有后续行为,例如打开另一个窗口,显示带有结果的JOptionPane,或者什么都不做。

要使用它:

 new ProgressiveSwingWorker<DataResultType, Object>("Editing some data", "Editing " + data.getSource()) { @Override protected DataResultType doInBackground() throws Exception { return retrieve(data.getSource()); } @Override protected void afterProgressBarDisposed(DataResultType results) { new DataEditor(results); } }.execute(); 

这显示了抽象类如何很好地提供模板操作,与定义API合约的接口概念正交。

它取决于你的要求和实施的力量,这是非常重要的。 你对这个问题有很多答案。 我认为这个问题是抽象类是API的发展。 你可以在抽象类中定义将来的函数定义,但是你不需要在你的主类中实现所有的函数定义,但是你不能做这个事情。