簡體   English   中英

為什么接口首選抽象類?

[英]Why are interfaces preferred to abstract classes?

我最近參加了一次采訪,他們問我“為什么接口比抽象類更受歡迎?”

我嘗試給出一些答案,如:

  • 我們只能獲得一個Extends功能
  • 他們是100%抽象
  • 實施不是硬編碼的

他們讓我帶走你使用的任何JDBC api。 “為什么他們是接口?”。

我可以為此得到更好的答案嗎?

面試問題反映了提問者的某種信念。 我相信這個人是錯的,因此你可以選擇其中一個方向。

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

他們想要的答案,嗯,其他海報突出了那些令人難以置信的好。 多接口繼承,繼承強制類做出實現選擇,接口可以更容易地更改。

但是,如果您在分歧中創建引人注目(且正確)的論點,那么面試官可能會注意到。 首先,強調關於接口的積極的事情,這是必須的。 其次,我會說在許多情況下界面更好,但它們也導致代碼重復,這是一個負面的事情。 如果您有大量的子類,它們將在很大程度上執行相同的實現,以及額外的功能,那么您可能需要一個抽象類。 它允許您擁有許多具有細粒度細節的類似對象,而只有接口,您必須擁有許多不同的對象,幾乎重復的代碼。

接口有很多用途,並且有令人信服的理由相信它們“更好”。 但是,您應始終使用正確的工具來完成作業,這意味着您無法注銷抽象類。

一般來說,這絕不是一個應該盲目遵循的“規則”,最靈活的安排是:

interface
   abstract class
       concrete class 1       
       concrete class 2

界面有幾個原因:

  • 已擴展的現有類可以實現接口(假設您可以控制現有類的代碼)
  • 現有的類可以是子類,子類可以實現接口(假設現有的類是可子類化的)

這意味着您可以使用預先存在的類(或者只是必須從其他類擴展的類)並讓它們與您的代碼一起使用。

抽象類用於提供具體類的所有公共位。 從編寫新類或修改要擴展它的類時開始擴展抽象類(假設它們從java.lang.Object擴展)。

您應該始終(除非您有充分的理由不)將變量(實例,類,本地和方法參數)聲明為接口。

你只能獲得一次繼承。 如果您創建一個抽象類而不是一個接口,那么繼承您的類的人也不能繼承另一個抽象類。

您可以實現多個接口,但只能從單個類繼承

抽象類

1.不能獨立於派生類進行實例化。 抽象類構造函數僅由其派生類調用。

2.定義基類必須實現的抽象成員簽名。

3.比接口更具可擴展性,不會破壞任何版本兼容性。 使用抽象類,可以添加所有派生類都可以繼承的其他非抽象成員。

4.可以包括存儲在字段中的數據。

5.允許具有實現的(虛擬)成員,因此,為派生類提供成員的默認實現。

6.從抽象類中獲取使用了一個子類的唯一基類選項。

接口

1.無法實例化。

2.接口的所有成員的實現發生在基類中。 在實現類中只能實現一些成員是不可能的。

3.使用其他成員擴展接口會破壞版本兼容性。

4.無法存儲任何數據。 只能在派生類上指定字段。 解決方法是定義屬性,但沒有實現。

5.所有成員都是自動虛擬的,不能包含任何實現。

6.雖然不會出現默認實現,但實現接口的類可以繼續相互派生。

devinb和其他人提到時,聽起來像面試官表示他們不接受你的有效答案而無知。

但是,提到JDBC可能是一個暗示。 在這種情況下,也許他們要求客戶端編碼對接口而不是類的好處。

因此,他們可能正在尋找一個更像“將客戶與特定實現分離 ”的答案,而不是完全有效的答案,例如“你只使用一次繼承”這些答案。

抽象類有許多潛在的缺陷。 例如,如果重寫方法,則除非顯式調用,否則不會調用super()方法。 這可能會導致實現不良的重寫類出現問題。 此外,使用繼承時equals()可能存在問題。

當您想要共享實現時,使用接口可以鼓勵使用組合。 組合通常是重用其他對象的更好方法,因為它不那么脆弱。 繼承很容易被濫用或用於錯誤的目的。

定義一個接口是一個非常安全的定義對象應該如何行事,而不會危及配備擴展了另一個類,抽象的或不脆性方式。

另外,正如您所提到的,您一次只能擴展一個類,但您可以根據需要實現任意數量的接口。

繼承實現時使用抽象類,繼承規范時使用接口。 該JDBC標准規定“的連接必須這樣做”。 這是規格。

使用抽象類時,可以在子類和基類之間創建耦合。 這種耦合有時會使代碼變得非常難以改變,特別是隨着子類數量的增加。 接口沒有這個問題。

您也只有一個繼承,因此您應該確保使用它是出於正當的原因。

“為什么接口比Abstract類更受歡迎?”

其他帖子在查看接口和抽象類之間的差異方面做得很好,所以我不會重復這些想法。

但是看一下采訪問題,更好的問題是“ 什么時候接口應該優先於抽象類?” (反之亦然)。

與大多數編程結構一樣,它們可用是有原因的,而面試問題中的絕對語句往往會錯過這一點。 它讓我想起你曾經讀過的關於C語言中goto語句的所有陳述。“你永遠不應該使用goto - 它揭示了糟糕的編碼技巧。” 但是, goto總是有其適當的用途。

恭敬地不同意上面的大部分海報(對不起!如果你願意,請把我改為:-))


首先,“只有一個超級”的答案是蹩腳的。 任何在面試中給我答案的人都會很快被反駁“在Java和C ++有多個超類之前存在C ++。為什么你認為James Gosling只允許一個超類用於Java?”

理解你的答案背后的哲學,否則你是敬酒(至少如果我采訪你。)


其次,接口與抽象類相比具有多個優勢,尤其是在設計接口時。 最大的一個是沒有強加給方法調用者的特定類結構。 沒有什么比嘗試使用需要特定類結構的方法調用更糟糕的了。 這是痛苦和尷尬。 使用接口可以以最小的期望將任何東西傳遞給方法。

例:

public void foo(Hashtable bar);

public void foo(Map bar);

對於前者,調用者將始終采用其現有的數據結構並將其關閉為新的Hashtable。


第三,接口允許具體類實現者中的公共方法是“私有的”。 如果未在接口中聲明該方法,則該方法不能被沒有使用該方法的業務的類使用(或誤用)。 這讓我想到了第4點......


第四,接口表示實現類和調用者之間的最小契約。 這個最小的合同確切地說明了具體實施者的預期使用方式,而不是更多。 不允許調用類使用接口的“契約”未指定的任何其他方法。 正在使用的接口名稱也會影響開發人員對如何使用該對象的期望。 如果開發人員通過了

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

開發人員知道他們可以調用的唯一方法是訪問方法。 他們不會被混凝土課程中明亮閃亮的方法分散注意力,他們不應該混淆。


最后,抽象類有許多方法,它們實際上僅用於要使用的子類。 所以抽象類往往看起來有點像外部開發人員的混亂,沒有關於外部代碼打算使用哪些方法的指導。

是的,當然可以保護一些這樣的方法。 但是,遺憾的是,受保護的方法對於同一個包中的其他類也是可見的。 如果抽象類的方法實現了接口,則該方法必須是公共的。

然而,在查看抽象超類或具體類時,使用接口所有這些內容都被安全地隱藏起來。


是的我知道開發人員當然可以使用一些“特殊”知識將對象轉換為另一個更廣泛的接口或具體類本身。 但這樣的演員違反了預期的合同,開發商應該被鮭魚打耳光。

這是“多重繼承”的問題。 我們可以通過另一個類“擴展”不超過一個abstarct類,但在Interfaces中,我們可以在單個類中“實現”多個接口。 因此,盡管Java一般不提供多重繼承,但通過使用接口,我們可以在其中包含multiplt繼承屬性。

希望這可以幫助!!!

如果他們認為X比Y更好,我不會擔心得到這份工作,我不願意為那些強迫我設計一個而不是另一個設計的人工作,因為他們被告知接口是最好的。 兩者都取決於具體情況,否則為什么語言選擇添加抽象類? 當然,語言設計師比我更聰明。

interface不能代替抽象類

比較喜歡

interface:通過多個不相關的對象實現合同

抽象類:在多個相關對象之間實現相同或不同的行為


有關接口和抽象類的用例,請參閱此相關的SE問題

接口與抽象類(通用OO)

使用案例:

如果必須使用Template_method模式,則無法使用interface實現。 應該選擇抽象類來實現它。

如果必須為許多未被刪除的對象實現一個功能,則抽象類不能用於此目的,您必須選擇接口

interface s是一種編寫純抽象類的更簡潔的方法。 你可以說實現沒有潛入(當然你可能想在某些維護階段這樣做,這會使接口變壞)。 就是這樣。 客戶端代碼幾乎沒有區別。

JDBC是一個非常糟糕的例子。 詢問任何試圖實現接口並在JDK版本之間維護代碼的人。 JAX-WS更糟糕,在更新版本中添加了方法。

存在技術差異,例如能夠乘以“繼承”接口。 這往往是設計混亂的結果。 在極少數情況下,擁有與接口層次結構不同的實現層次結構可能很有用。

在接口的缺點,編譯器無法接受一些不可能的強制轉換/ instanceof

上面沒有提到的一個原因。

您可以使用java.lang.reflect.Proxy輕松地修飾任何接口,允許您在運行時將自定義代碼添加到給定接口中的任何方法。 它非常強大。

有關教程,請參見http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html

抽象類提供了一種定義行為模板的方法,用戶可以在其中插入詳細信息。

一個很好的例子是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契約的接口概念正交。

它取決於您的要求和實施的力量,這是非常重要的。 你對這個問題有很多答案。 我對這個問題的看法是抽象類是API的演變。 您可以在抽象類中定義未來的函數定義,但是您不需要在主類中進行所有函數實現,但是使用接口您無法做到這一點。

您可以實現多個接口,但特別是對於c#,您不能具有多個繼承

因為接口不會強制您進入某些繼承層次結構。

當您只需要某個對象實現某些方法但您不關心其譜系時,可以定義接口。 因此,有人可以擴展現有的類來實現接口,而不會影響該類以前存在的行為。

這就是JDBC是所有接口的原因; 您並不真正關心JDBC實現中使用的類,您只需要任何JDBC實現就可以獲得相同的預期行為。 在內部,Oracle JDBC驅動程序可能與PostgreSQL驅動程序有很大不同,但這與您無關。 可能必須繼承數據庫開發人員已經擁有的一些內部類,而另一個可能是從頭開始完全開發的,但這對您來說並不重要,只要它們都實現相同的接口以便您可以與一個或者其他人不知道其中任何一個的內部運作。

好吧,我建議問題本身應該改寫。 接口主要是類獲得的合同,合同本身的實現會有所不同。 抽象類通常包含一些默認邏輯,其子類將添加更多邏輯。 我會說問題的答案取決於鑽石問題。 Java可以防止多重繼承以避免它。 http://en.wikipedia.org/wiki/Diamond_problem )。

他們讓我帶走你使用的任何JDBC api。 “為什么他們是接口?”。

我對這個具體問題的回答是:

SUN不知道如何實現它們或在實現中放入什么。 由服務提供商/ db供應商決定將他們的邏輯放入實現中。

JDBC設計與Bridge模式有關,它表示“將抽象與其實現分離,以便兩者可以獨立變化”。

這意味着無論jdbc供應商提供或使用的實現層次結構如何,都可以發展JDBC api的接口層次結構。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM