組合 vs 繼承是經常被問到的面試問題之一。你可能也聽過要使用組合而不是繼承。
組合 vs 繼承
組合和繼承都是面向對象編程的概念。它們與任何特定的編程語言(如Java)無關。在我們進行程序上的組合與繼承比較之前,讓我們先快速定義一下它們。
組合
組合是面向對象編程中實現對象之間有一個關係的設計技術。在Java中,通過使用其他對象的實例變量來實現組合。例如,在Java面向對象編程中,具有工作的人可以像下面這樣實現。
package com.journaldev.composition;
public class Job {
// 變量,方法等
}
package com.journaldev.composition;
public class Person {
// 組合有一個關係
private Job job;
// 變量,方法,構造函數等。面向對象
繼承
繼承是物件導向程式設計中實現物件間”is-a”關係的設計技術。在Java中,繼承是使用extends關鍵字實現的。例如,在java編程中,Cat是一個Animal關係,將被實現如下所示。
package com.journaldev.inheritance;
public class Animal {
// 變數,方法等
}
package com.journaldev.inheritance;
public class Cat extends Animal{
}
組合優於繼承
組合和繼承都通過不同的方法促進代碼重用。那麼該選擇哪一種呢?如何比較組合和繼承。你一定聽過在編程中應該優先選擇組合而不是繼承。讓我們看看一些原因,這將幫助你選擇組合還是繼承。
-
繼承是緊密耦合的,而組合則是鬆散耦合的。假設我們有以下具有繼承關係的類別。
package com.journaldev.java.examples; public class ClassA { public void foo(){ } } class ClassB extends ClassA{ public void bar(){ } }
為了簡單起見,我們將超類和子類都放在單個包中。但通常它們會在不同的代碼庫中。可能有許多類擴展了超類 ClassA。這種情況的一個非常常見的例子是擴展 Exception 類。現在假設 ClassA 的實現如下所示,添加了一個新方法 bar()。
package com.journaldev.java.examples; public class ClassA { public void foo(){ } public int bar(){ return 0; } }
一旦開始使用新的 ClassA 實現,您將在 ClassB 中獲得編譯時錯誤,如
The return type is incompatible with ClassA.bar()
。解決方法是更改超類或子類的 bar() 方法使它們兼容。如果您使用組合而不是繼承,則永遠不會遇到此問題。使用組合的 ClassB 實現的一個簡單示例可能如下所示。class ClassB{ ClassA classA = new ClassA(); public void bar(){ classA.foo(); classA.bar(); } }
-
繼承中沒有訪問控制,而在組合中可以限制訪問。我們向所有具有對子類訪問權限的其他類公開所有超類方法。因此,如果在超類中引入新方法或存在安全漏洞,子類就變得容易受到攻擊。由於在組合中我們可以選擇使用哪些方法,因此比繼承更安全。例如,我們可以使用以下代碼在 ClassB 中向其他類提供 ClassA foo() 方法的訪問權限。
class ClassB { ClassA classA = new ClassA(); public void foo(){ classA.foo(); } public void bar(){ } }
這是組合優於繼承的一個主要優勢之一。
-
組合在多個子類情況下提供了調用方法的靈活性。例如,假設我們有以下繼承情況。
abstract class Abs { abstract void foo(); } public class ClassA extends Abs{ public void foo(){ } } class ClassB extends Abs{ public void foo(){ } } class Test { ClassA a = new ClassA(); ClassB b = new ClassB(); public void test(){ a.foo(); b.foo(); } }
那麼如果有更多的子類,組合會通過為每個子類有一個實例使我們的代碼變得難看嗎?不,我們可以像下面這樣重寫Test類。
class Test { Abs obj = null; Test1(Abs o){ this.obj = o; } public void foo(){ this.obj.foo(); } }
這將使您能夠根據構造函數中使用的對象使用任何子類。
-
組合相對於繼承的另一個好處是測試範圍。在組合中進行單元測試很容易,因為我們知道我們從另一個類別使用了哪些方法。我們可以模擬它進行測試,而在繼承中,我們嚴重依賴於超類,並不知道超類的所有方法將被使用。因此,我們將不得不測試超類的所有方法。這是額外的工作,我們不必因為繼承而做這些不必要的工作。
這就是組合與繼承的比較。您已經有足夠的理由選擇組合而不是繼承。僅當您確定超類不會更改時才使用繼承,否則請使用組合。
Source:
https://www.digitalocean.com/community/tutorials/composition-vs-inheritance