iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 21
1
Modern Web

讓 TypeScript 成為你全端開發的 ACE!系列 第 21

Day 21. 機動藍圖・靜態成員 X 即刻操作 - Static Properties & Methods

https://ithelp.ithome.com.tw/upload/images/20190921/20120614qXmtKKXHPs.png

閱讀本篇文章前,仔細想想看

  1. 如何使用類別的繼承(Inheritance)?
  2. 為何我們設計類別的成員時,會儘量以 private 模式為基準?什麼時候該開放成員給外部使用呢?
  3. privateprotected 模式間的最大差別在哪呢?
  4. 通常子類別建構物件時,要如何連結父類別初始化物件的邏輯呢?
  5. 使用 super 有沒有特別需要注意的事項呢?

如果還沒理解完畢的話,可以先翻看前一篇文章喔!

今天講的東西不會像昨天討論類別繼承一樣 —— 篇幅太大。(筆者也被折騰了一番

正文開始

類別的靜態屬性與靜態方法 Static Properties & Methods

靜態跟動態意義差別在哪?

首先,有些人可能對靜態(Static)與動態(Dynamic)這兩個詞在類別的概念會搞混。不過大部分在講 OOP 時我們不會刻意提到動態屬性與方法,那是因為我們早就在使用了。

其實最簡單的想法是這樣:

類別新增物件時的成員變數與成員方法 —— 皆可被當成動態屬性與方法

所以那些類別成員們(Class Members)都是被形容成動態的概念,筆者是這麼看待的。

那為何還要分動靜態?靜態又是什麼概念?

假設想要寫一個類別是專門計算幾何圖形中 —— 跟圓形有關的類別,以下簡單的把程式碼寫出來進行示範。

https://ithelp.ithome.com.tw/upload/images/20190921/20120614wBXy6bBz4H.png

以上的程式碼的說明如下。

CircleGeometry 的成員變數事實上有兩個(不要看到建構子函式裡面只有一個參數就認定只有一個成員變數喔!):

  • PI 被設為 private 模式代表圓周率 3.14
  • radius 代表圓的半徑,並且需要經過建構子進行初始化的動作

CircleGeometry 的成員方法也有兩個:

  • area 代表計算圓的面積,其型別為 (): number
  • circumference 代表計算圓的周長,其型別也是 (): number

這邊筆者認為讀者可以自行試試看如何使用這個類別建立物件並且呼叫方法,所以以下的程式碼暫且就不測了。

https://ithelp.ithome.com.tw/upload/images/20190921/201206149j5dOF4erJ.png

好的!上述的 CircleGeometry 的成員們為何會被認定並非靜態的主要原因是:在建構物件的時候,我們的成員變數的值會不定

譬如可以建立半徑為 2 的圓 —— new CircleGeometry(2);也可以建構半徑為 2.71828 的圓 —— new CircleGeometry(2.71828) 以及更多無限種案例。每一次建立半徑不同的圓,物件的成員方法 areacircumference 也會計算出不同的結果。

儘管類別藍圖提供的成員們都是一樣的格式,但是每一次建構出的新物件 —— 該物件的值以及呼叫方法出現的結果可能也會變得不同,因此讓人感覺到物件的狀態是動態的

如果想要了解相對於物件的狀態是動態的意義,必須把焦點轉換為:把類別本身看待成一個物件 —— 類別本身的屬性與方法,這些東西就被稱作是靜態屬性與方法

類別本身的屬性與方法

筆者換個角度來解釋靜態屬性與方法。

讀者應該看過 JavaScript 裡面的 Math 這個物件吧 ~ 但它跟普通類別的使用方式不同 —— 它不需要用 new 去建構物件然後呼叫方法,而是直接提供 Math 本身的屬性與方法讓開發者使用

https://ithelp.ithome.com.tw/upload/images/20190921/201206141nhKr1KNLH.png

我們可以稱 Math.PI 中的 PIMath 這個類別的靜態屬性(Static Property);相對地,那些 Math.randomMath.sinMath.pow 與更多從 Math 延伸出來的方法都統稱為 Math 這個類別的靜態方法(Static Methods)。

重點 1. 類別的靜態屬性與方法

不需要經由建構物件的過程,而是直接從類別本身提供的屬性與方法,皆稱之為靜態屬性與方法,又被稱為靜態成員(Static Members)。

因此,靜態成員具有一個很重要的特點:不管物件被建立多少次,靜態成員只會有一個版本 —— 這也符合靜態的概念:固定、單一版本、不變的原則等等。

通常會使用靜態成員的狀況:

  1. 靜態成員不會隨著物件建構的不同而隨之改變
  2. 靜態成員可以作為類別本身提供的工具,不需要經過建構物件的程序;換句話說:類別提供之靜態成員本身就是可被操作的介面

宣告靜態成員 static

想要仿造 Math 類別 —— 我們可以使用 static 關鍵字將 CircleGeometry 裡的成員轉換成靜態成員們。

https://ithelp.ithome.com.tw/upload/images/20190921/20120614cWc7QdFQzV.png

要注意的是:由於 PI 從成員變成了靜態成員,因此不能使用 this.PI,而是 StaticCircleGeometry.PI 來取用。

那是因為:靜態成員是綁在類別身上,並非類別建立起來的物件 —— 因為不需要經過 new 建立物件的過程就可以從類別身上取用!

再來,因為捨棄了原本建構新物件時,必須提供 radius 作為物件的成員變數這個管道 —— 沒了物件本身建構後的 radius 屬性,所以必須改寫 areacircumference 這兩個方法:將型別從原本的 (): number 變成 (radius: number): number,並且從 public 成員方法變成靜態方法。

以下比對 CircleGeometryStaticCircleGeometry 之間的使用差別。(編譯過後並且使用 node 執行結果如圖一)

https://ithelp.ithome.com.tw/upload/images/20190921/20120614Nb2dej3OPo.png

https://ithelp.ithome.com.tw/upload/images/20190921/20120614aGAXeQ1zIh.png
圖一:結果都ㄧ樣,但實踐的過程不ㄧ樣罷了

重點 2. 宣告與使用靜態屬性與方法 Static Properties & Methods

若想要在某類別 C 宣告的靜態屬性 Pstatic 與方法 Mstatic,程式碼格式如下:

https://ithelp.ithome.com.tw/upload/images/20190921/20120614hXG2XlkkJa.png

若想使用類別 C 的靜態屬性 Pstatic 與方法 Mstatic,則可以直接從 C 呼叫:C.PstaticC.Mstatic(/* 參數... */)

static 與存取修飾子 Access Modifiers

事實上,靜態成員跟普通類別成員有類似的地方 —— 都是可以設定存取模式

假設想要防止使用者竄改 StaticCircleGeometry 裡面的靜態屬性 PI 之值,並且提供另一個管道跟讓使用者得知它的值為多少,我們可以這麼做:

https://ithelp.ithome.com.tw/upload/images/20190921/20120614r2G4IZEg0P.png

讀者可以發現 private static 的概念很簡單:private 模式可以封裝我們的屬性與方法,加在 static 旁邊就只是告訴開發者不能亂動這個靜態成員。因此類別裡面是可以使用靜態屬性 —— 因此 areacircumference 這兩個方法裡面就算去呼叫 StaticCircleGeometry 是不會出現錯誤的,但是如果你在外面呼叫就會被 TypeScript 警告喔!(被 TypeScript 查驗結果如圖二;錯誤訊息如圖三)

https://ithelp.ithome.com.tw/upload/images/20190921/20120614R5y0IP1hPY.png
圖二:強行取得 private 模式下的靜態屬性或方法,也是會被記警告的!

https://ithelp.ithome.com.tw/upload/images/20190921/201206148oBT9tEoHB.png
圖三:TypeScript 很明確就跟你講了,屬性 PIprivate 模式,只能在類別內部取用呢!

靜態成員這個功能可是非常實用的!

重點 3. 靜態成員與存取修飾子 Static Members & Access Modifiers

類別的靜態成員可以被設定不同的存取模式 —— 包含 publicprivate 以及 protected 模式。其運作的方式跟普通成員變數與方法的流程一模一樣;差別就在於 —— 普通成員是綁定在建構過後的物件上,而靜態成員則是跟類別本身綁定。

上一篇:火車票務系統案例

還記得上一篇的範例嗎?筆者短暫把必要的程式碼部分截取下來:

https://ithelp.ithome.com.tw/upload/images/20190921/201206143NbJySXvkr.png

事實上,我們必須訂立站點與站點間的資訊 stationsDetail 與站點的列表 stops。不管車票 TrainTicket 被建立多少次,但站點資訊是不太可能隨之變動的

因此可以將這兩個成員設定成靜態成員。

https://ithelp.ithome.com.tw/upload/images/20190921/201206148VgQw7dQNw.png

不過把成員從普通的狀態變成類別的靜態成員過程中,必須要把所有跟靜態成員相關的程式碼,原本是用 this 去呼叫就得改成用類別 TrainTicket 去呼叫喔

因此裡面的 isStopExist 函式取用到 stops 這個屬性就必須被改成靜態屬性的呼叫方式:

https://ithelp.ithome.com.tw/upload/images/20190921/20120614rRTMXooHpZ.png

deriveDuration 則是因為有取用到 stationsDetail 屬性,因此也必須要被轉換:

https://ithelp.ithome.com.tw/upload/images/20190921/20120614WYtRMyPlwJ.png

讀者試試看

筆者剛剛展示過:將 TrainTicket 裡的 stopsstationsDetail 更改為靜態屬性。讀者可以自行編譯並且驗證程式碼的運行。

另外,除了可將 stopsstationsDetail 改成靜態屬性外,讀者也可以確認看看,將這兩種屬性從 private 轉成 public 並且測看看能不能在外面呼叫。而且如果這些屬性可以在外面呼叫的話,讀者甚至可以自訂車票的站點表來測測看不同的結果 —— 體會一下靜態成員的語法運作過程喔。

當然,創意一點的方式是:能不能夠寫簡單的靜態方法,進行站點更改或新增站點的工作?這些就可以留給讀者發想~

小結

今天的主題應該比昨天簡單很多,畢竟繼承的概念本來就多到炸筆者也沒想到會寫到篇幅太長

下一篇也很簡單,筆者要介紹存取方法 Access Methods~

不過筆者照樣要提醒,剛入門 OOP 的讀者們,會建議來回複習類別的基礎,因為後面還有一大堆還沒講完。目前已經提到的有:

  • Class 的基礎 / Member Variables & Methods
  • Access Modifiers
  • Class Inheritance & super
  • Static Properties & Methods

記得,下一篇要講到的 Access Methods 與之前講完的 Access Modifiers,儘管都有 Access 這個字,可是意義天差地遠啊~可不要搞混了。


上一篇
Day 20. 機動藍圖・類別繼承 X 延用設計 - TypeScript Class Inheritance
下一篇
Day 22. 機動藍圖・特殊成員 X 存取方法 - TypeScript Class Accessors
系列文
讓 TypeScript 成為你全端開發的 ACE!51

尚未有邦友留言

立即登入留言