javascript設計模式之鴨子類型和多態

本文參考曾探編寫的JavaScript設計模式與開發實踐

設計模式的實現都遵循一條原則,即“找出程序中變化的地方,並將變化封裝起來”。一個程序的設計總是可以分為可變的部分和不變的部分。當我們找出可變的部分,並且把這些部分封裝起來,那麼剩下的就是不變和穩定的部分。這些不變和穩定的部分是非常容易復用的。這也是設計模式為什麼描寫的是可復用面向對象軟件基礎的原因。

1.鴨子類型

鴨子類型的通俗說法是:“如果它走起路來像鴨子,叫起來也是鴨子,那麼它就是鴨子。”

鴨子類型專業解釋:例如,在不使用鴨子類型的語言中,我們可以編寫一個函數,它接受一個類型為"鴨子"的對象,並調用它的"走"和"叫"方法,但它隻能接受鴨子類型的對象,否則報錯。在使用鴨子類型的語言中,這樣的一個函數可以接受一個任意類型的對象,並調用它的"走"和"叫"方法。如果這些需要被調用的方法不存在,那麼將引發一個運行時錯誤。任何擁有這樣的正確的"走"和"叫"方法的對象都可被函數調用這種行為就是符合鴨子類型。

所以如果弱類型語言(js)函數需要接收參數,為保證健壯性,則應先判斷參數類型,並判斷參數是否包含需要訪問的方法、屬性。隻有當這些條件滿足時,程序才真正處理調用參數的方法、參數

    var duck = {
            sing: function() {
                console.log('嘎嘎嘎');
            }
        }
        var chicken = {
            sing: function() {
                console.log('嘎嘎嘎');
            }
        }
        var choir = [] //合唱團
        function joinChoir(duck) {
            if (duck && typeof duck.sing === 'function') {
                choir.push(duck)
                console.log('合唱隊添加瞭一個成員');
            }
        }
        joinChoir(duck)
        joinChoir(chicken)
            // 大合唱
        for (let i = 0; i < choir.length; i++) {
            choir[i].sing()
        }

2.多態

2.1 java多態

對面向對象來說,多態分為編譯時多態和運行時多態。其中編譯時多態是靜態的,主要是指方法的重載,它是根據參數列表的不同來區分不同的方法。通過編譯之後會變成兩個不同的方法,在運行時談不上多態。而運行時多態是動態的,它是通過動態綁定來實現的,也就是大傢通常所說的多態性。

在java裡,多態是同一個行為具有不同表現形式或形態的能力,即對象多種表現形式的體現,就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。在簡單來說,編譯時對象是父類類型,到真正運行時,對象才可以知道具體是哪個子類類型,才知道調用哪個子類中實現的方法

Java 實現多態有 3 個必要條件:繼承、重寫和向上轉型。隻有滿足這 3 個條件,開發人員才能夠在同一個繼承結構中使用統一的邏輯實現代碼處理不同的對象,從而執行不同的行為。

  • 繼承:在多態中必須存在有繼承關系的子類和父類。
  • 重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
  • 向上轉型:在多態中需要將子類的引用賦給父類對象,隻有這樣該引用才既能可以調用父類的方法,又能調用子類的方法。

由此可以得出使用多態的好處,我們可以很好的完成代碼的解耦和工作,加強代碼的可擴展性,是代碼更加靈活,在不改變原有接口方法的情況下簡化流程等,總結一下就是:

  • 減耦合
  • 增強可以替換性
  • 可擴展性
  • 靈活性等…

舉個生活例子,如下圖所示:使用手機掃描二維碼支付時,二維碼並不知道客戶是通過何種方式進行支付,隻有通過二維碼後才能判斷是走哪種支付方式執行對應流程。

在這裡插入圖片描述

舉個代碼例子,創建 Figure 類,在該類中首先定義存儲二維對象的尺寸,然後定義有兩個參數的構造方法,最後添加 area() 方法,該方法計算對象的面積。代碼如下:

public class Figure {
    double dim1;
    double dim2;
    Figure(double d1, double d2) {
        // 有參的構造方法
        this.dim1 = d1;
        this.dim2 = d2;
    }
    double area() {
        // 用於計算對象的面積
        System.out.println("父類中計算對象面積的方法,沒有實際意義,需要在子類中重寫。");
        return 0;
    }
}

創建繼承自 Figure 類的 Rectangle 子類,該類調用父類的構造方法,並且重寫父類中的 area() 方法。代碼如下:

public class Figure {
    double dim1;
    double dim2;
    Figure(double d1, double d2) {
        // 有參的構造方法
        this.dim1 = d1;
        this.dim2 = d2;
    }
    double area() {
        // 用於計算對象的面積
        System.out.println("父類中計算對象面積的方法,沒有實際意義,需要在子類中重寫。");
        return 0;
    }
}

創建繼承自 Figure 類的 Triangle 子類,該類與 Rectangle 相似。代碼如下:

public class Rectangle extends Figure {
    Rectangle(double d1, double d2) {
        super(d1, d2);
    }
    double area() {
        System.out.println("長方形的面積:");
        return super.dim1 * super.dim2;
    }
}

創建 Test 測試類,在該類的 main() 方法中首先聲明 Figure 類的變量 figure,然後分別為 figure 變量指定不同的對象,並調用這些對象的 area() 方法。代碼如下:

public class Rectangle extends Figure {
    Rectangle(double d1, double d2) {
        super(d1, d2);
    }
    double area() {
        System.out.println("長方形的面積:");
        return super.dim1 * super.dim2;
    }
}

從上述代碼可以發現,無論 figure 變量的對象是 Rectangle 還是 Triangle,它們都是 Figure 類的子類,因此可以向上轉型為該類,從而實現多態。

2.2 js多態

多態的實際含義:是同一操作作用於不同的對象上面,可以產生不同的解釋和不同的執行結果。換句話說,給不同的對象發送同一個消息的時候,這些對象會根據這個消息分別給出不同的反饋。

來舉例說明一下多態的實際含義:

主人傢裡養瞭兩隻動物,分別是一隻鴨和一隻雞,當主人向它們發出“叫”的命令時,鴨會“嘎嘎嘎”地叫,而雞會“咯咯咯”地叫。這兩隻動物都會以自己的方式來發出叫聲。它們同樣“都是動物,並且可以發出叫聲”,但根據主人的指令,它們會各自發出不同的叫聲。

      var Duck = function() {
        }
        Duck.prototype.sing = function() {
            console.log('嘎嘎嘎');
        }
        var Chicken = function() {
        }
        Chicken.prototype.sing = function() {
            console.log('嘎嘎嘎');
        }
        function sing(animal) {
            if (animal && (typeof animal.sing === 'function')) {
                animal.sing()
            }
        }
        sing(new Duck())
        sing(new Chicken())

多態背後的思想:是將“做什麼”和“誰去做以及怎樣去做”分離開來,也就是將“不變的事物”與 “可能改變的事物”分離開來。在這個故事中,動物都會叫,這是不變的,但是不同類型的動物具體怎麼叫是可變的。把不變的部分隔離出來,把可變的部分封裝起來,這給予瞭我們擴展程序的能力,程序看起來是可生長的,也是符合開放—封閉原則的,相對於修改代碼來說,僅僅增加代碼就能完成同樣的功能,這顯然優雅和安全得多。

多態的實現:多態的思想實際上是把“做什麼”和“誰去做”分離開來,要實現這一點,歸根結底先要消除類型之間的耦合關系。如果類型之間的耦合關系沒有被消除,那麼我們在makeSound 方法中指定瞭發出叫聲的對象是某個類型,它就不可能再被替換為另外一個類型。在 Java 中,可以通過向上轉型來實現多態。而 JavaScript 的變量類型在運行期是可變的。一個JavaScript 對象,既可以表示 Duck 類型的對象,又可以表示 Chicken 類型的對象,這意味著 JavaScript 對象的多態性是與生俱來的。這種與生俱來的多態性並不難解釋。JavaScript 作為一門動態類型語言,它在編譯時沒有類型檢查的過程,既沒有檢查創建的對象類型,又沒有檢查傳遞的參數類型。

多態的最根本好處:你不必再向對象詢問“你是什麼類型”而後根據得到的答案調用對象的某個行為——你隻管調用該行為就是瞭,其他的一切多態機制都會為你安排妥當。換句話說,多態最根本的作用就是通過把過程化的條件分支語句轉化為對象的多態性,從而消除這些條件分支語句

多態的最根本好處,可以用下面這個例子很好地詮釋:在電影的拍攝現場,當導演喊出“action”時,主角開始背臺詞,照明師負責打燈光,後面的群眾演員假裝中槍倒地,道具師往鏡頭裡撒上雪花。在得到同一個消息時,每個對象都知道自己應該做什麼。如果不利用對象的多態性,而是用面向過程的方式來編寫這一段代碼,那麼相當於在電影開始拍攝之後,導演每次都要走到每個人的面前,確認它們的職業分工(類型),然後告訴他們要做什麼。如果映射到程序中,那麼程序中將充斥著條件分支語句。利用對象的多態性,導演在發佈消息時,就不必考慮各個對象接到消息後應該做什麼。對象應該做什麼並不是臨時決定的,而是已經事先約定和排練完畢的。每個對象應該做什麼,已經成為瞭該對象的一個方法,被安裝在對象的內部,每個對象負責它們自己的行為。所以這些對象可以根據同一個消息,有條不紊地分別進行各自的工作。

將行為分佈在各個對象中,並讓這些對象各自負責自己的行為,這正是面向對象設計的優點。

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: