1. 程式人生 > >javascript面向物件系列第四篇——OOP中的常見概念

javascript面向物件系列第四篇——OOP中的常見概念

前面的話

  面向物件描述了一種程式碼的組織結構形式——一種在軟體中對真實世界中問題領域的建模方法。本文將從理論層面,介紹javascript面向物件程式程式(OOP)中一些常見的概念

物件

  所謂物件,本質上就是指事物(包括人和物)在程式設計語言中的表現形式。這裡的事物可以是任何東西(如某個客觀存在的物件,或者某些較為抽象的概念)。例如,對於貓這種常見物件來說,具有某些明確的特徵(如顏色、名字、體型等),能執行某些動作(如喵喵叫、睡覺、躲起來、逃跑等)。在OOP語義中,這些物件特徵都叫做屬性,而那些動作則被稱為方法

  此外,還有一個口語方面的類比:物件往往是用名詞表示的(如book、person),方法一般都是些動詞(如read、run),屬性值則往往是一些形容詞

“The black cat sleeps on my head”

  cat是一個物件,black是一個顏色屬性值,sleep代表一個動作,也就是OOP語義中的方法。on my head是動作sleep的限定條件。因此它可以當做傳遞給sleep方法的一個引數

  在現實生活中,相似物件之間往往都有一些共同的組成特徵。例如蜂鳥和老鷹都具有鳥類的特徵,因此它們可以被統稱為鳥類。在OOP中,類實際上就是物件的設計藍圖或製作配方。物件這個詞,有時候也叫做例項。所以,老鷹是鳥類的一個例項。可以基於同一個類創建出許多不同的物件,因為類更多的是一種模板。而物件則是在這些模板的基礎上被創建出來的實體

  javascript實際上壓根沒有類。該語言的一切都是基於物件的,其依靠的是一套原型(prototype)系統。而原型本身實際上也是一種物件

  在傳統的面嚮物件語言中,基於Person類建立了一個Match的新物件,而在javascript中,則是將現有的Person物件擴充套件成一個Match的新物件

封裝

  封裝主要用於闡述物件中所包含的內容。封裝概念通常由兩部分組成:相關的資料(用於儲存屬性)、基於這些資料所能做的事(所能呼叫的方法)

  封裝的目的是將資訊隱藏,即方法與屬性的可見性。一般而言,封裝包括封裝資料和封裝實現

  在許多語言的物件系統中,封裝資料是由語法解析來實現的,這些語言提供了public、private、protected這些關鍵字來限定方法和屬性的可見性,這種限定分類定義了物件使用者所能訪問的層次

  但javascript並沒有提供對這些關鍵字的支援,只能依賴變數的作用域來實現封裝特性, 而且只能模擬出 public 和 private 這兩種封裝性。除了ECMAScript6中提供的let之外,一般通過函式來建立作用域:

var myObject = (function(){
  var  name = 'match';    // 私有(private)變數
  return {
    getName: function(){    // 公開(public)方法
      return  name;
    }
  }
})();
console.log( myObject.getName() );// 輸出:match
console.log( myObject.name )    // 輸出:undefined

  面向物件程式設計強調的是資料和操作資料的行為本質上是互相關聯的,因此好的設計就是把資料以及和它相關的行為封裝起來。舉例來說,用來表示一個單詞或者短語的一串字元通常被稱為字串。字元就是資料。但是關心的往往不是資料是什麼,而是可以對資料做什麼,所以可以應用在這種資料上的行為(計算長度、新增資料、搜尋等等)都被設計成 String 類的方法。所有字串都是 String 類的一個例項,也就是說它是一個包裹,包含字元資料和可以應用在資料上的函式

  封裝不僅僅是隱藏資料,還包括隱藏實現細節、設計細節以及隱藏物件的型別等

  從封裝實現細節來講,封裝使得物件內部的變化對其他物件而言是透明的,也就是不可見的。 物件對它自己的行為負責。其他物件或者使用者都不關心它的內部實現。封裝使得物件之間的耦合變鬆散,物件之間只通過暴露的 API 介面來通訊。當修改一個物件時,可以隨意地修改它的內部實現,只要對外的介面沒有變化,就不會影響到程式的其他功能

聚合

  所謂聚合,有時候也叫做組合,實際上是指將幾個現有物件合併成一個新物件的過程。總之,這個概念強調的是將多個物件合而為一的能力。通過聚合這種強有力的方法,可以將一個問題分解成多個更小的問題。這樣一來,問題就會顯得更易於管理(便於各個擊破)。當一個問題域的太過複雜時,就可以考慮將它分解成若干個子問題區,並且必要的話,這些問題區還可以再繼續分解成更小的分割槽。這樣做有利於從幾個不同的抽象層次來考慮這個問題

  類似的情況如Book是由一個或多個author物件,publisher物件、若干chapter物件以及一組table物件等組合而成的物件

繼承

  通過繼承這種方法,可以非常優雅地實現對現有程式碼的重用。在傳統的OOP環境中,繼承通常指的是類與類之間的關係,但由於javascript中不存在類,因此它的繼承只能發生在物件之間

  比如,有一個Person的一般性物件,其中包含一些姓名、性別之類的屬性,以及一些功能性函式,如步行、談話、睡覺、吃飯等。然後,需要一個Programmer物件時,可以讓Programmer繼承自Person,Programmer物件只需要實現屬於它自己的那部分特殊功能(如編寫程式碼),而其餘部分重用Person的實現即可

  當一個物件繼承自另一個物件時,通常會往其中加入新的方法,以擴充套件被繼承的老物件。通常將這一過程稱之為“B繼承自A”或“B擴充套件自A”。另外對於新物件來說,它可以根據自己的需要,從繼承的那組方法中選擇幾個來重新定義。這樣做並不會改變物件的介面,因為其方法名是相同的,只不過當呼叫新物件時,該方法的行為與之前不同了。這種重定義繼承方法的過程叫做覆寫

多型

  多型一詞源於希臘文polymorphism,拆開來看是poly(複數)+morph(形態)+ism,從字面意思可以理解為複數形態

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

  Programmer物件繼承了上一級物件Person的所有方法。這意味著這兩個物件都實現了"talk"等方法。現在,程式碼中有一個叫做“Match”的變數,即使是在不知道它是一個Person物件還是一個Programmer物件的情況下,也依然可以直接呼叫該物件的"talk"方法,而不必擔心這會影響程式碼的正常工作。類似這種不同物件通過相同的方法呼叫來實現各自行為的能力,稱之為多型

  多型背後的思想是將“做什麼”和“誰去做以及怎樣去做”分離開來,也就是將“不變的事物”與“可能改變的事物”分離開來。把不變的部分隔離出來,把可變的部分封裝起來,這給予了我們擴充套件程式的能力,程式看起來是可生長的,也符合開放——封閉原則,相對於修改程式碼來說,僅僅增加程式碼就能完成同樣的功能,這顯然優雅和安全得多

  多型最根本的作用是通過把過程化的條件分支語句轉化為物件的多型性,從而消除這些條件分支語句

總結

  下面來對上面提到的概念進行總結

  物件:Match是一個男人(後者是一個物件)

  屬性:Match是男性,黃面板,黑頭髮

  方法:Match能吃飯、睡覺、喝水、做夢

  類:Match是Programmer類的一個例項

  原型物件:Match是一個由Programmer物件擴充套件而來的新物件

  封裝:Match物件包含了資料和基於這些資料的方法

  聚合:Match只是整個Web開發團隊物件的一部分,該團隊還包括一個Designer物件Wang,以及一個ProjectManager物件Li

  繼承:Designer、ProjectManager、Programmer都是分別擴充套件自Person物件的新物件

  多型:可以隨時呼叫Match、Wang、Li這三個物件各自的talk方法,它們都可以正常工作,儘管這些方法會產生不同的結果。如Match可能談得更多的是程式碼的效能,Wang更傾向於談程式碼的優雅性,而Li強調的是最後期限。總之,每個物件都可以重新自定義它們的繼承方法talk