理解多態特性

多態(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