iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
1
Modern Web

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

Day 22. 機動藍圖・特殊成員 X 存取方法 - TypeScript Class Accessors

https://ithelp.ithome.com.tw/upload/images/20190921/201206144pwPj9UtLu.png

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

  1. 類別的靜態成員(Static Members)是什麼?與普通成員差異在哪?
  2. 什麼情況下會採用靜態成員的設計呢?

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

有些沒有 OOP 經驗的讀者可能覺得 —— 類別要講的東西怎麼特別多

是很多沒錯 XD —— 會用倒是覺得挺好用的~ 尤其結合 TypeScript 介面可以寫出還蠻牛逼的程式碼。

因此本篇就~正文開始吧!

取值方法與存值方法 Accessors

特殊的類別成員

筆者照樣用簡單的方式舉例,就以昨天講到的 CircleGeometry 類別進行延伸。以下是它的程式碼:

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

今天來搞一些神奇的功能 —— 假設每次計算圓形的面積時,與其呼叫物件的 area 方法,我們能不能夠改成類似呼叫物件屬性的方式計算出面積呢?

一種可行方式是這樣:

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

於是可以這樣使用 CircularGeometryV2。(編譯並且用 node 執行結果如圖一)

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

https://ithelp.ithome.com.tw/upload/images/20190921/20120614oYZeyyFJtw.png
圖一:正常地印出半徑為 2 的圓之面積的結果

不過這樣有兩個缺點:

  1. 我們可以直接用 randomCircle.area = 某數字 來強行覆寫掉面積的值,使得計算結果被破壞掉
  2. 在建構子裡面進行運算事實上是不建議的,因為建構子的目的是初始化物件的成員變數們

當然可以仿造過往的手法,把 area 換成 private 模式,並且宣告 getArea 的公用方法讓使用者可以呼叫並取得 area 的值。但這就跟本篇一開始想要實踐出的結果相違背啊!

於是筆者就把今天的主角請出場~存取方法(Accessors)。

使用存取方法 Accessors

存取方法分成兩種:取值存值,也是時常聽到的 Getter Methods 或 Setter Methods

貼心小提醒

儘管在 JS 裡將 Accessors 存取方法分別稱為 Getter/Setter Methods(取值/存值);然而某部分語言會將 Accessors 代表取值方法,而 Mutators 則是代表存值方法 —— 因此不是用 Getter/Setter 名稱之分,而是以 Accessor/Mutator 這樣的名稱來分。

根據今天想要達成的事情:想要藉由呼叫物件的 area 屬性取得圓的面積的值 —— 這裡的關鍵功能是『 取值 』,因此筆者先來介紹 Getter Method 怎麼使用。

以下是正確的實踐方式:

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

讀者可以發現,取值方法的實踐比想像中的簡單,就是get 關鍵字搭配想要取的名稱

再次測試使用 CircleGeometryV2 的結果如圖二。

https://ithelp.ithome.com.tw/upload/images/20190921/201206143La4G9Hp4W.png
圖二:使用 get area 的特殊取值方法結果正常

若我們強行對 area 進行覆寫的動作,會被 TypeScript 送出罰單。(檢測結果如圖三;錯誤訊息如圖四)

https://ithelp.ithome.com.tw/upload/images/20190921/20120614Dg0EKv95qy.png
圖三:TypeScript 會警告你,屬性 area 是不能被覆寫的

https://ithelp.ithome.com.tw/upload/images/20190921/20120614dAEiuXL1FX.png
圖四:其中,TypeScript 吿訴我們的訊息很有趣 —— area 是唯讀的!(Read-only)

這裡出現了一個很有趣的結果:area 是唯讀的屬性,也就是說 —— 如果單純只有定義取值方法(Getter Method),它本身就是唯讀的狀態

另外,使用存取方法(Accessors)令人感到方便的地方是 —— 只要物件的狀態被改變,存取方法們計算過後的值也會被自動更新!

主要是因為,一但那些藉由存取方法定義過後的物件屬性 —— 呼叫該屬性時,會根據存取方法裡面寫的程式進行計算

所以測試以下的程式碼,radius 的屬性的值被改變後,area 屬性不需要主動去寫程式碼更新狀態,它就會根據取值方法(Getter Method)裡的內容計算出來。(以下程式碼編譯與 node 執行結果如圖五)

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

https://ithelp.ithome.com.tw/upload/images/20190921/20120614LXO6XdLuuL.png
圖五:儘管 radius 被強行改變,我們的 area 也跟著被計算出來

讀者試試看

試著把 circumference 專門計算圓形周長的方法改成用 Getter Method 的方式實踐出來。

另外,我們還可以使用存值方法(Setter Methods)去更改物件屬性被指派值的狀況。這是什麼意思?

假設想將剛剛的 CircleGeometryV2area 屬性 —— 原本是唯讀狀態 —— 改成每一次被指派值時,其半徑值 radius 也會跟著被調整成正確的值。因此,筆者這裡就直接示範使用 Setter Method 來達成剛剛所敘述的功能。

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

從以上的程式碼可以看出 —— 相對於取值方法 Getter Method,定義一個存值方法 Setter Method 則是用 set 關鍵字!

其中,要注意的一點是:因為存值方法專門在模擬屬性被指派值的情況因此會需要一個參數去代表被指派的值,所以存值方法的型別會多一個參數 (value: number): void

以下筆者來驗證看看 CircleGeometryV2 的存取 area 這個屬性的行為會不會連同物件的半徑 radius 被更改。(編譯並且使用 node 執行之結果如圖六)

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

https://ithelp.ithome.com.tw/upload/images/20190921/20120614lrXVHTuCb5.png
圖六:對 area 指派值,radius 也自動被更改了!

存取方法的型別推論與註記 Accessors Type Inference & Annotation

另外,筆者必須提到跟型別系統有關的重點(畢竟這可是 TypeScript 的主打 Feature 啊!)。

由於在 area 對應的取值方法(Setter Method)裡,其參數對應的是數字 number 型別 —— 也就是說,如果指派錯誤的型別到 area 去,TypeScript 也會自動幫我們監控喔!(偵測結果如圖七;訊息如圖八)

https://ithelp.ithome.com.tw/upload/images/20190921/20120614nDgOEC6N2P.png
圖七:如果指派不為 number 型別的值到 area 屬性,由於 area 的存值方法的參數特別被註記為 number 型別,因此會出現錯誤

https://ithelp.ithome.com.tw/upload/images/20190921/20120614Y2Y4KjpeBW.png
圖八:很明顯地,TypeScript 告訴我們不能將字串丟進去

相對於存值方法,取值方法也是有型別推論與註記的機制。如果對於函式型別篇章夠熟悉的話,TypeScript 會自動推論函式的輸出型態

所以以下的程式碼,areaOfCircle 應該會被自動推論為數字 number 型別。(推論結果如圖九)

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

https://ithelp.ithome.com.tw/upload/images/20190921/20120614ycE6xuPf1p.png
圖九:取得 area 的值時,也會被自動地被 TypeScript 推論出型別來喔!

存取方法的限制

取值方法(Getter Method)不能有任何參數:因為是模擬呼叫屬性的方式進行物件的取值,當然不會有參數的出現喔!(錯誤訊息如圖十)

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

https://ithelp.ithome.com.tw/upload/images/20190921/20120614e2pqvANsJv.png
圖十:TypeScript 扳著臉跟你講,get Accessor 不能有任何參數

另外,取值方法的用意是模擬呼叫物件的屬性,因此 —— 取值方法沒有回傳任何值是錯誤的行為!(檢測結果如圖十一)

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

https://ithelp.ithome.com.tw/upload/images/20190921/201206141YLPKfIBrZ.png
圖十一:TypeScript 會自動告訴你,你忘記回傳值了!

相對地,存值方法(Setter Method)只能有一個參數:因為是模擬指派任何值到屬性的方式進行物件的存值,而指派任何值只會被當成一個參數。(以下程式碼錯誤訊息如圖十二)

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

https://ithelp.ithome.com.tw/upload/images/20190921/20120614UNb65Kq61E.png
圖十二:TypeScript 也會提醒你不能有更多參數在 set Accessor 裡面喔

重點 1. 類別的存取方法 Accessors

分成兩種:取值方法(Getter Method)與存值方法(Setter Method)。

  1. 取值方法專門在模擬呼叫物件的屬性時的行為;存值方法則是在模擬指派值到物件屬性的行為:由於兩者皆是用方法的方式來呈現屬性的呼叫與指派行為,因此才會被稱為存取方法。(而不是存取屬性)
  2. 若只有單純實踐某物件屬性的取值方法(Getter Method)而沒有相對應的存值方法,該屬性可以模擬唯讀(Read-only)的狀態
  3. 取值方法的實踐不能有任何參數。若某屬性是利用取值方法來模擬的話,呼叫該物件的屬性,型別推論的結果會等同於取值方法回傳的值之型別。又因為是在模擬物件取值的過程,因此不回傳值的行為也是錯誤的
  4. 存值方法只能有一個參數,而該參數代表的值是指派的值。根據函式型別篇章提出的重點,我們必須對存值方法內部的參數進行積極註記的動作。若某屬性的指派行為是用存值方法模擬,則該屬性被指派錯誤的值也會根據存值方法的參數被註記到的型別進行比對。
  5. 若想要在類別 C 宣告某存取方法模擬物件呼叫或指派值到屬性 P,其中 P 必須被指派的值之型別為 Tassign,則程式碼的格式為:

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

題外話:readonly 也是可以在類別內使用的

剛剛提到可以單純實踐取值方法(Getter Method)就可以模擬唯讀(Read-only)屬性的行為。

不過,在介面與型別裡可以用的 readonly 關鍵字在類別裡的成員變數裡也可以用

假設我們的 CircleGeometryV2 裡 —— 圓周率 PI 原本是 private 模式,但是想要讓它既可以開放讀取但又可以防止被更改的話,可以直接加註為 readonly 並且宣告為 public 模式喔!

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

我們可以在外面取得 PI 的值但是確保它不會被更改。(檢測結果如圖十三;錯誤訊息如圖十四)

https://ithelp.ithome.com.tw/upload/images/20190921/20120614yFua7jXGf0.png
圖十三:如果 PI 被覆寫的話,就會被 TypeScript 提醒!

https://ithelp.ithome.com.tw/upload/images/20190921/20120614iXWVNf940h.png
圖十四:TypeScript 提醒你,物件的 PI 屬性是 read-only 狀態喔

再者,不只是普通的成員變數,連**類別的靜態屬性也可以被標註為唯讀模式**呢~

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

所以如果強行覆寫 CircleGeometryV2 就會被警告呢。(檢測結果如圖十五;錯誤訊息如圖十六)

https://ithelp.ithome.com.tw/upload/images/20190921/20120614mcG3S2gRp8.png
圖十五:有標註 readonly 就可以防止被外部覆寫

https://ithelp.ithome.com.tw/upload/images/20190921/20120614csrYlq9NGP.png
圖十六:CircleGeometryV2.staticPI 是唯讀(read-only)狀態,錯誤訊息也寫得好好的

重點 2. 類別裡使用 readonly

可以在類別的成員變數(Member Variables)與靜態屬性(Static Properties)標註 readonly,代表該成員變數或靜態屬性是唯讀狀態。

小結

今天我們又把類別的一項功能講完了 —— 也就是存取方法(Accessor)。

下一篇要講到一個很有趣的設計模式應用 —— 單例模式(Singleton Pattern),明天見~~~


上一篇
Day 21. 機動藍圖・靜態成員 X 即刻操作 - Static Properties & Methods
下一篇
Day 23. 機動藍圖・私有建構子 X 單身狗模式 - Private Constructor & Singleton Pattern
系列文
讓 TypeScript 成為你全端開發的 ACE!51
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言