物件導向是什麼?|JavaScript 四大物件特性簡介|初學者筆記(1)

更新日期: 2023 年 3 月 1 日

小弟身為一位初學 JS 不到兩個月的超級新人,學到一個程度時總會覺得觀念卡卡的。

後來發現原來是自己對於 JS 「物件導向」此概念不太理解,導致我在學習一些與物件相關的程式碼時,總是無法清楚理解它在幹嘛。

遇到這種問題,天真如我當然馬上去 google 「物件導向」這關鍵字。

結果點開查詢結果後,發現內容寫的都是「有字天書」。

每個字我都懂,可是合起來的話就不知道在講什麼了。

後來有幸看到幾部相關的影片,才讓我對「物件導向」的觀念有清楚的認識。

閱讀要求:

「物件導向」屬於一種進階的程式設計概念。

如果你想要看懂本文的內容,建議先對以下內容有基本認識,才能更容易閱讀本文的內容:

什麼是物件導向程式設計?

根據當我們搜尋「物件導向程式設計」(Object Oriented Programming,簡稱 OOP)時,通常都會達到類似以下的解釋:

它將物件作為程式的基本單元,將程式和資料封裝其中,以提高軟體的重用性、靈活性和擴充性,物件裡的程式可以存取及經常修改物件相關連的資料。

引述來自《維基百科

作為一個程式初學者,通常第一直覺都是:「到底在寫什麼碗糕」。

因此,容我們換個更容易理解的方式,去認識物件導向的概念。

(本影片有中文 CC 字幕,請切換觀賞)

我們以 Google 這間公司為例。

Google 旗下有各式各樣的軟體服務,如:google 搜尋、google 地圖、google 日曆、Youtuebe 等等。

如果我們將這些服務的程式碼相加,它一共約有 20 億行程式碼需保存,並且讓全公司 2 萬 5 千位員工維護並更新。

對此,我們需要藉由有條理與有秩序的方式,將程式碼好好的收納並安排整齊。

首先,我們第一個可以想到的方法就是透過「函式」,將他們分裝組合起來。

假設一個函式可以保存 10 行程式碼,就能將 20 億行程式碼拆成 2 億個函式。

當然,對我們來說 2 億個函式,也是一個龐大的數量。

這時就需要使用另一個更大的容器,將函式打包起來,此做法就稱為:「物件」

我們可以使用物件,將數個函式通通收納到同一個區塊。

假如一個物件可以收納 100 個函式,這樣就能將 2 億個函式拆成 200 萬個物件。

相較於原先的 20 億行程式碼,200 萬個物件已經容易維護許多。

因此,我們換個方式重新解說一次物件導向

你可能想說 200 萬還是太多了,該怎麼辦?

這還不簡單,如果物件無法將程式碼分類完全,那我們為何不再用「更大的物件」將其打包。

例如:大大大物件 -> 大大物件 -> 大物件 -> 物件 -> 函式-> 程式碼。

最後程式碼就能透過函式與物件,讓它像俄羅斯娃娃一樣不斷被打包收納與整理。

上述這種將程式碼透過不斷物件打包、分類的過程,就稱為「物件導向」。

因此,其實「物件導向」的本質,其實就是一種管理程式碼的「管理方法」

當我們理解物件導向的概念後,再套用到實際的程式碼中,就能理解對應的概念。

當然,以上的內容只是簡短的介紹,物件導向程式設計其實具備以下四種重要概念:

  • 封裝(Encapsulation)
  • 提煉(abstraction)
  • 繼承(Inheritance)
  • 多型(Polymorphism)

接下來,將為你們仔細講解相關意涵。

封裝(Encapsulation)

未使用物件導向概念以前

通常我們一開始學習 JavaScript 時,會用「變數」儲存資料,用「函式」執行動作。

此外,我們還會知道程式語言處理問題時,是將大問題猜解成小問題,並針對各自的小問題撰寫對應的功能函數。

當每個小困難都順利被解決時,整體問題也會一併迎刃而解。

述這種拆解問題的過程,也是計算機科學的一個重要方法:功能分解(functional decomposition)。

未使用物件的程式碼具有的屬性
未使用物件的程式碼具有的屬性

對於初學者來說,上述的編程方法非常簡單與直觀。

但問題是當程式碼變得越來越長、複雜時,為了要調用散落在各處的函數與變數,讓管理與後續維護變得異常困難。

例如,每當有一個新功能要加入原有代碼中時,就需要先釐清函數之間的關係,才不會產生改 A 錯 B 的現象

圖上這種函數之間複雜,常被形容成義大利麵代碼,意思指都攪再一起,難以分開。
圖上這種函數之間複雜,常被形容成義大利麵代碼,意思指都攪再一起,難以分開。

具備物件導向概念後

物件導向程式設計,就是要改善上述所列的問題。

它透過將性質相同的變數與函數,放進同一個「容器」中,用以區分不同特性的代碼。

物件的示意圖
物件的示意圖

此外,我們將每個物件中變數與函式都稱為物件的「成員」。

如果該成員是變數的話,就會被稱為「屬性」(property),如果該成員是函式,就被稱為「方法」(method)

物件成員的名稱:屬性與方法

舉例來說,如果我們今天想要建立一個「黑崎一護」的物件。

它的屬性就會有「姓名」、「穿著」、「死神執照」等等。

它的方法則會有月牙天沖、瞬步、卍解等等。

死神的物件範例
死神的物件範例

如果以實際程式碼來看,假設我們有個計算薪資相關的代碼。

let baseSalary = 26,400; //底薪
let overtime = 10; //加班時數
let rate = 176; //每小時加班費

function getWage(baseSalary, overtime, rate){
  return baseSalary + (overtime*rate); //薪資加總
}

getWage(); // 執行函式,計算薪資

如果我們將上面的程式碼「封裝」,則會得到一個「員工」的物件,並且呈現方式改成以下顯示:

let employee = {
  baseSalary: 26,400, //底薪
  overtime: 10, //加班時數
  rate: 176, //每小時加班費
  getWage: function(){
    return this.baseSalary + (this.overtime*this.rate); //薪資加總
  }
}

employee.getWage() // 執行函式,計算薪資

這段程式碼「封裝前」與「封裝後」,最大的差別在於函式的「參數數量」。

在未封裝前,薪資加總的函式一共有三個參數,分別有三個參數:

  • 底薪參數
  • 加班時數參數
  • 加班費參數

但當我們使用物件封裝後,可以使用物件特有的功能關鍵字「this」語法,讓電腦知道物件中的成員彼此相關。

因此將薪資加總的函式參數瞬間歸零,變成一個更簡單、易使用的參數。

最好的函式,就是沒有任何參數。

Robert C. Martin

提煉(abstraction)

「提煉」是物件導向中非常重要的概念,他會幫助我們將原有包山包海的程式碼中,提取出真正重要的部份讓人使用。

我們以實際情境為例,每個人的手中都有一台智慧型手機,通常它大概只有三個按鈕:開機、音量大聲、音量小聲。

這種設計模式,幫助我們隱藏這台手機的複雜性。

讓使用者能更用正確、輕鬆的方法與這台手機互動,而不用去理解手機內部複雜的電線迴路設計。

智慧型手機從內部複製雜的電線迴路,提煉出一個簡單的操作介面
智慧型手機從內部複製雜的電線迴路,提煉出一個簡單的操作介面

同理,套用回程式碼中。

原本包含數個函式變數的物件,透過「提煉」將一些對使用者不重要的內容隱藏,只保留重要的部分。

提煉物件的過程示意圖
提煉物件的過程示意圖

這種作法,不僅讓使用者能正確的操作物件,選擇適當的方法與屬性。

同時,因為只有顯示部分的物件成員,能避免因為使用者操作不當,導致物件內的其他成員連帶受影響。

提煉物件的好處
提煉物件的好處

繼承(Inheritance)

當我們在建立物件時,有時候會遇到物件具備相同的屬性與方法,只差在「值」不同而已

一般來說,程式新手都會使用「複製、貼上大法」,如果有幾個的物件就複製貼上幾次

但更簡單的方式,是使用物件導向的「繼承」特性。先設定一個公版的物件代碼,再以此創建相近性質的物件。

這種方式可以讓程式碼的冗長程度降低,並增加效率。

例如,如果我們想要建立一個法鬥與吉娃娃的物件。

由於它們都具備相同的屬性與方法,可以先建立一個公版的「狗」物件,並以該物件為基礎,各自提供對應的值,就可以建立完成。

使用狗物件原型,建立不同品種的狗物件

多型(Polymorphism)

有些時候,我們有些函數的名稱可能會類似,但實際執行的作用卻不相同。

我們繼續以狗狗為例,當狗狗見到主人時,都一定會有「見到主人」對應的反應。

但此行為會隨著不同品種、不同個性的小狗,而有不同內容的實際作為。

function 法鬥見到主人的反應(){
  // 不理人之類的...
}

function 吉娃娃見到主人的反應(){
  // 亂叫之類的...
}

function 柴犬見到主人的反應(){
  // 搖尾巴之類的...
}


在使用物件之前,我們會需要寫各式各樣的條件判斷式,例如 if、switch 等等語法,去區分不同情況應該適用何種函式

if( 狗 == 法鬥 ){
  法鬥見到主人的反應();
} else if( 狗 == 吉娃娃 ){
  吉娃娃見到主人的反應();
} else if( 狗 == 柴犬 ){
  柴犬見到主人的反應();
} 

但當我們使用「物件」將相近的變數與函式歸類在一起時,就可以免除上述複雜的判斷條件。

例如下方有三個不同品種的狗狗物件:

三種不同品種的狗狗物件

如果我們想要增加見主人的函示,只要各自新增一個名稱相同方法。

在不同物件中,增加相同名稱的方法,增加函式易用性

當我們實際應使用時,只要藉由引用物件的方法,就可以輕易完成函數的使用,也不用再去思考各式各樣的函式名稱與判斷條件。

法鬥.見主人(); //法鬥見主人的反應
吉娃娃.見主人(); //吉娃娃見主人的反應
柴犬.見主人(); //吉娃娃見主人的反應

而此方法就稱為「多型」,同一個命名卻能有多種形式的展現。

概念總結

以上四個基本觀念,就是物件導向程式設計所帶來的好處。

最後,讓我們再次總結四大特性。

  • 封裝:降低複雜程度 + 增加可重複使用性
  • 簡化:降低使用難度門檻 + 保護區塊重要資訊不受影響
  • 繼承:減少重複相近的程式碼
  • 多型:重新打造複雜的條件判斷式

以上就是這次分享的內容,感謝收看!

問題統整

什麼是物件

假設一個函式可以保存 10 行程式碼,就能將 20 億行程式碼拆成 2 億個函式。

當然,對我們來說 2 億個函式,也是一個龐大的數量。

這時就需要使用另一個更大的容器,將函式打包起來,此做法就稱為:「物件」。

什麼是物件導向

其實「物件導向」的本質,其實就是一種管理程式碼的「管理方法」。

例如:大大大物件 -> 大大物件 -> 大物件 -> 物件 -> 函式-> 程式碼。

最後程式碼就能透過函式與物件,讓它像俄羅斯娃娃一樣不斷被打包收納與整理。

Similar Posts