为什么接口首选抽象类?

时间:2009-03-12 17:15:24

标签: java oop interface abstraction

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

我试着给出一些答案,如:

  • 我们只能获得一个扩展功能
  • 他们是100%抽象
  • 实施不是硬编码的

他们让我带走你使用的任何JDBC api。 “为什么他们是接口?”。

我可以为此获得更好的答案吗?

23 个答案:

答案 0 :(得分:55)

面试问题反映了提问者的某种信念。我相信这个人是错的,因此你可以选择其中一个方向。

  1. 给他们想要的答案。
  2. 恭敬地不同意。
  3. 他们想要的答案,好吧,其他海报突出了那些令人难以置信的好。 多接口继承,继承强制类进行实现选择,接口可以更容易更改。

    但是,如果您在分歧中创建引人注目(且正确)的论点,那么面试官可能会注意到。 首先,强调关于接口的积极的事情,这是必须的。 其次,我会说在许多情况下界面更好,但它们也导致代码重复,这是一个负面的事情。如果您有大量的子类,它们将在很大程度上执行相同的实现,以及额外的功能,那么您可能需要一个抽象类。它允许您拥有许多具有细粒度细节的类似对象,而只有接口,您必须拥有许多不同的对象,几乎重复的代码。

    界面有很多用途,有令人信服的理由相信它们“更好”。但是,您应该始终使用正确的工具来完成工作,这意味着您无法注销抽象类。

答案 1 :(得分:24)

总的来说,这绝不是一个应该盲目遵循的“规则”,最灵活的安排是:

interface
   abstract class
       concrete class 1       
       concrete class 2

界面的存在有以下几个原因:

  • 已经扩展的现有类可以实现接口(假设您可以控制现有类的代码)
  • 现有的类可以是子类,子类可以实现接口(假设现有的类是可子类化的)

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

抽象类用于提供具体类的所有公共位。从编写新类或修改要扩展它的类时开始扩展抽象类(假设它们从java.lang.Object扩展)。

你应该总是(除非你有充分的理由不这样做)将变量(实例,类,本地和方法参数)声明为接口。

答案 2 :(得分:23)

你只获得一次继承。如果你创建一个抽象类而不是一个接口,那么继承你的类的人也不能继承一个不同的抽象类。

答案 3 :(得分:10)

您可以实现多个接口,但只能从单个类继承

答案 4 :(得分:10)

抽象类

1.不能独立于派生类进行实例化。抽象类构造函数仅由其派生类调用。

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

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

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

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

6.从抽象类中获取使用了一个子类的唯一基类选项。

<强> 接口

1.无法实例化。

2.接口的所有成员的实现发生在基类中。在实现类中只能实现一些成员。

3.扩展与其他成员的接口会破坏版本兼容性。

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

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

6.虽然不会出现默认实现,但实现接口的类可以继续相互派生。

答案 5 :(得分:7)

正如devinb和其他人提到的那样,听起来像面试官表示他们不接受你的有效答案而无知。

但是,提到JDBC可能是一个暗示。在这种情况下,他们可能会要求对界面而不是类进行 客户端 编码的好处。

因此,除了完全有效的答案,例如“你只能使用继承”,这与课程设计有关,他们可能正在寻找更像“解耦的答案”客户来自特定的实施“

答案 6 :(得分:5)

抽象类有许多潜在的缺陷。例如,如果重写方法,则除非您明确调用它,否则不会调用super()方法。这可能会导致实现不良的重写类出现问题。此外,使用继承时equals()可能存在问题。

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

定义一个接口是一种非常安全的方式来定义一个对象应该如何行动,而不会冒着可以来扩展另一个类,抽象或不抽象的脆弱性。

另外,正如您所提到的,您一次只能扩展一个类,但您可以根据需要实现任意数量的接口。

答案 7 :(得分:4)

继承实现时使用抽象类,继承规范时使用接口。 JDBC标准声明“连接必须 this ”。这是规范。

答案 8 :(得分:3)

使用抽象类时,可以在子类和基类之间创建耦合。这种耦合有时会使代码变得非常难以改变,特别是随着子类数量的增加。接口没有这个问题。

您也只有一个继承,因此您应该确保使用它是出于正当的原因。

答案 9 :(得分:3)

  

“为什么接口优先于   抽象类?“

其他帖子在查看接口和抽象类之间的差异方面做得很好,所以我不会重复这些想法。

但是看一下采访问题,更好的问题是“何时接口应该优先于抽象类?” (反之亦然)。

与大多数编程结构一样,它们是有原因的,而面试问题中的绝对语句往往会错过。它让我想起你曾经读过的有关C中 goto 语句的所有陈述。“你永远不应该使用 goto - 它揭示了糟糕的编码技巧。”但是, goto 总是有适当的用途。

答案 10 :(得分:3)

恭敬地不同意上面的大部分海报(对不起!如果你愿意的话,请把我打倒:-))


首先,“只有一个超级”的答案是蹩脚的。任何在面试中给我答案的人都会很快被反驳“在Java和C ++有多个超类之前存在C ++。为什么你认为James Gosling只允许一个超类用于Java?”

理解你的答案背后的哲学,否则你是敬酒(至少如果我采访你。)


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

示例:

public void foo(Hashtable bar);

VS

public void foo(Map bar);

对于前者,调用者将始终使用他们现有的数据结构并将其关闭到新的Hashtable中。


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


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

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

开发人员知道他们可以调用的唯一方法是访问方法。他们不会因具体课堂中明亮闪亮的方法而分心,他们不应该把它弄得乱七八糟。


最后,抽象类有许多方法,实际上只存在要使用的子类。所以抽象类往往看起来有点像外部开发人员的混乱,没有指导外部代码使用哪些方法。

是的,当然可以保护一些此类方法。但是,遗憾的是,受保护的方法对于同一个包中的其他类也是可见的。如果抽象类的方法实现了接口,则该方法必须是公共的。

然而,在查看抽象超级类或具体类时,使用接口所有这些内容都被安全地隐藏起来。


是的我知道开发人员当然可以使用一些“特殊”知识将对象转换为另一个更广泛的接口或具体类本身。但是这样的演员违反了预期的合同,开发商应该被鲑鱼拍打。

答案 11 :(得分:2)

这是“多重继承”的问题。 我们可以通过另一个类“扩展”不超过一个abstarct类,但在Interfaces中,我们可以在单个类中“实现”多个接口。 因此,虽然Java一般不提供多重继承,但是通过使用接口,我们可以在其中包含multiplt继承属性。

希望这有帮助!!!

答案 12 :(得分:2)

如果他们认为X比Y好,我不会担心得到这份工作,我不愿意为那些强迫我设计一个而不是另一个设计的人工作,因为他们被告知界面是最好的。两者都取决于具体情况,否则为什么语言选择添加抽象类?当然,语言设计师比我聪明。

答案 13 :(得分:1)

界面不能替代抽象类

体型

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

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

有关接口和抽象类的用例,请参阅此相关的SE问题

Interface vs Abstract Class (general OO)

用例:

如果必须使用Template_method模式,则无法使用界面实现。应该选择抽象类来实现它。

如果你必须为许多未被删除的对象实现一个功能,抽象类不能达到目的,你必须选择接口

答案 14 :(得分:1)

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

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

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

答案 15 :(得分:1)

interface是一种编写纯粹抽象类的更简洁的方法。你可以说实现没有潜入(当然你可能想在某些维护阶段这样做,这会使接口变坏)。就是这样。客户端代码几乎没有区别。

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

存在技术差异,例如能够乘以“继承”界面。这往往是设计混乱的结果。在极少数情况下,拥有一个与接口层次结构不同的实现层次结构可能很有用。

在接口的缺点上,编译器无法接收一些不可能的强制转换/ instanceof

答案 16 :(得分:0)

好吧,我建议问题本身应该改写。接口主要是类获得的合同,合同本身的实现会有所不同。抽象类通常包含一些默认逻辑,其子类将添加更多逻辑。 我会说问题的答案取决于钻石问题。 Java可以防止多重继承以避免它。 (http://en.wikipedia.org/wiki/Diamond_problem)。

答案 17 :(得分:0)

当您只需要某个对象实现某些方法但您不关心其谱系时,可以定义接口。因此,有人可以扩展现有的类来实现接口,而不会影响该类以前存在的行为。

这就是JDBC所有接口的原因;您并不真正关心JDBC实现中使用的类,您只需要任何JDBC实现就可以获得相同的预期行为。在内部,Oracle JDBC驱动程序可能与PostgreSQL驱动程序有很大不同,但这与您无关。可能必须继承数据库开发人员已经拥有的一些内部类,而另一个可能是从头开始完全开发的,但这对您来说并不重要,只要它们都实现相同的接口以便您可以与一个或者其他人不知道其中任何一个的内部运作。

答案 18 :(得分:0)

  

他们让我接受任何JDBC api   你用的。 “他们为什么   接口?”。

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

SUN不知道如何实现它们或在实现中放入什么。由服务提供商/数据库供应商决定将其逻辑纳入实现。

JDBC设计与Bridge模式有关系,它表示“将抽象与其实现分离,以便两者可以独立变化”。

这意味着无论jdbc供应商提供或使用的实现层次结构如何,都可以发展JDBC api的接口层次结构。

答案 19 :(得分:0)

抽象类提供了一种定义行为模板的方法,用户可以在其中插入详细信息。

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

我扩展了这个类,它自动创建了一个弹出进度条。我覆盖了done(),以控制弹出窗口的处理,但随后提供了一个新的覆盖点,允许用户可选地定义进度条消失后发生的事情。

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契约的接口概念正交。

答案 20 :(得分:0)

它取决于您的要求和实施的力量,这非常重要。 你对这个问题有很多答案。 我对这个问题的看法是抽象类是API的演变。 您可以在抽象类中定义未来的函数定义,但是您不需要在主类中进行所有函数实现,但是使用接口就无法做到这一点。

答案 21 :(得分:0)

因为接口不会强迫你进入某个继承层次结构。

答案 22 :(得分:0)

您可以实现多个接口,但特别是对于c#,您不能具有多个继承