理解多態特性
多態(Polymorphism)
學習完了封裝、抽象、繼承之後,我們再來看面向對象編程的最後一個特性,多態。多態是指,子類可以替換父類,在實際的代碼運行過程中,調用子類的方法實現。對於多態這種特性,純文字解釋不好理解,我們還是看一個具體的例子。
public class DynamicArray {
private static final int DEFAULT_CAPACITY = 10;
protected int size = 0;
protected int capacity = DEFAULT_CAPACITY;
protected Integer[] elements = new Integer[DEFAULT_CAPACITY];
public int size() { return this.size; }
public Integer get(int index) { return elements[index];}
//... 省略 n 多方法...
public void add(Integer e) {
ensureCapacity();
elements[size++] = e;
}
protected void ensureCapacity() {
//... 如果數組滿了就擴容... 代碼省略...
}
}
public class SortedDynamicArray extends DynamicArray {
@Override
public void add(Integer e) {
ensureCapacity();
for (int i = size-1; i>=0; --i) { // 保證數組中的數據有序
if (elements[i] > e) {
elements[i+1] = elements[i];
} else {
break;
}
}
elements[i+1] = e;
++size;
}
}
public class Example {
public static void test(DynamicArray dynamicArray) {
dynamicArray.add(5);
dynamicArray.add(1);
dynamicArray.add(3);
for (int i = 0; i < dynamicArray.size(); ++i) {
System.out.println(dynamicArray[i]);
}
}
public static void main(String args[]) {
DynamicArray dynamicArray = new SortedDynamicArray();
test(dynamicArray); // 打印結果:1、3、5
}
}
多態這種特性也需要編程語言提供特殊的語法機制來實現。在上面的例子中,我們用到了三個語法機制來實現多態。
第一個語法機制是編程語言要支持父類對象可以引用子類對象,也就是可以將 SortedDynamicArray 傳遞給 DynamicArray。
第二個語法機制是編程語言要支持繼承,也就是 SortedDynamicArray 繼承了 DynamicArray,才能將 SortedDyamicArray 傳遞給 DynamicArray。
第三個語法機制是編程語言要支持子類可以重寫(override)父類中的方法,也就是 SortedDyamicArray 重寫了 DynamicArray 中的 add () 方法。
通過這三種語法機制配合在一起,我們就實現了在 test () 方法中,子類 SortedDyamicArray 替換父類 DynamicArray,執行子類 SortedDyamicArray 的 add () 方法,也就是實現了多態特性。
對於多態特性的實現方式,除了利用 “繼承加方法重寫” 這種實現方式之外,我們還有其他兩種比較常見的的實現方式,一個是利用接口類語法,另一個是利用 duck-typing 語法。不過,並不是每種編程語言都支持接口類或者 duck-typing 這兩種語法機制,比如 C++ 就不支持接口類語法,而 duck-typing 只有一些動態語言才支持,比如 Python、JavaScript 等。
接下來,我們先來看如何利用接口類來實現多態特性。我們還是先來看一段代碼。
public interface Iterator {
String hasNext();
String next();
String remove();
}
public class Array implements Iterator {
private String[] data;
public String hasNext() { ... }
public String next() { ... }
public String remove() { ... }
//... 省略其他方法...
}
public class LinkedList implements Iterator {
private LinkedListNode head;
public String hasNext() { ... }
public String next() { ... }
public String remove() { ... }
//... 省略其他方法...
}
public class Demo {
private static void print(Iterator iterator) {
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
public static void main(String[] args) {
Iterator arrayIterator = new Array();
print(arrayIterator);
Iterator linkedListIterator = new LinkedList();
print(linkedListIterator);
}
}
在這段代碼中,Iterator 是一個接口類,定義了一個可以遍歷集合數據的迭代器。Array 和 LinkedList 都實現了接口類 Iterator。我們通過傳遞不同類型的實現類(Array、LinkedList)到 print (Iterator iterator) 函數中,支持動態的調用不同的 next ()、hasNext () 實現。
具體點講就是,當我們往 print (Iterator iterator) 函數傳遞 Array 類型的對象的時候,print (Iterator iterator) 函數就會調用 Array 的 next ()、hasNext () 的實現邏輯;當我們往 print (Iterator iterator) 函數傳遞 LinkedList 類型的對象的時候,print (Iterator iterator) 函數就會調用 LinkedList 的 next ()、hasNext () 的實現邏輯。
剛剛講的是用接口類來實現多態特性。現在,我們再來看下,如何用 duck-typing 來實現多態特性。我們還是先來看一段代碼。這是一段 Python 代碼。
class Logger:
def record(self):
print(“I write a log into file.”)
class DB:
def record(self):
print(“I insert data into db. ”)
def test(recorder):
recorder.record()
def demo():
logger = Logger()
db = DB()
test(logger)
test(db)
從這段代碼中,我們發現,duck-typing 實現多態的方式非常靈活。Logger 和 DB 兩個類沒有任何關係,既不是繼承關係,也不是接口和實現的關係,但是隻要它們都有定義了 record () 方法,就可以被傳遞到 test () 方法中,在實際運行的時候,執行對應的 record () 方法。
也就是說,只要兩個類具有相同的方法,就可以實現多態,並不要求兩個類之間有任何關係,這就是所謂的 duck-typing,是一些動態語言所特有的語法機制。而像 Java 這樣的靜態語言,通過繼承實現多態特性,必須要求兩個類之間有繼承關係,通過接口實現多態特性,類必須實現對應的接口。
多態特性講完了,我們再來看,多態特性存在的意義是什麼?它能解決什麼編程問題?
多態特性能提高代碼的可擴展性和複用性。爲什麼這麼說呢?我們回過頭去看講解多態特性的時候,舉的第二個代碼實例(Iterator 的例子)。
在那個例子中,我們利用多態的特性,僅用一個 print () 函數就可以實現遍歷打印不同類型(Array、LinkedList)集合的數據。當再增加一種要遍歷打印的類型的時候,比如 HashMap,我們只需讓 HashMap 實現 Iterator 接口,重新實現自己的 hasNext ()、next () 等方法就可以了,完全不需要改動 print () 函數的代碼。所以說,多態提高了代碼的可擴展性。
如果我們不使用多態特性,我們就無法將不同的集合類型(Array、LinkedList)傳遞給相同的函數(print (Iterator iterator) 函數)。我們需要針對每種要遍歷打印的集合,分別實現不同的 print () 函數,比如針對 Array,我們要實現 print (Array array) 函數,針對 LinkedList,我們要實現 print (LinkedList linkedList) 函數。而利用多態特性,我們只需要實現一個 print () 函數的打印邏輯,就能應對各種集合數據的打印操作,這顯然提高了代碼的複用性。
除此之外,多態也是很多設計模式、設計原則、編程技巧的代碼實現基礎,比如策略模式、基於接口而非實現編程、依賴倒置原則、裏式替換原則、利用多態去掉冗長的 if-else 語句等等。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/_ne4nu1qE3qNgXKkcfcYZA