iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 27
2
Modern Web

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

Day 27. 機動藍圖・策略模式 X 臨機應變 - Strategy Pattern Using TypeScript. II

https://ithelp.ithome.com.tw/upload/images/20190925/20120614BCmHgafZXm.png

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

大致上理解策略模式以及應用類別與介面進行實踐。

另外本篇會延續上一篇的範例,因此沒有看過可以先翻看前一篇的文章喔!

廢話不多說,正文開始吧!

善用策略模式 Strategy Pattern

前情提要

前一篇大致上利用 RPG 角色的範例,示範了如何簡單對各種不同的角色職業 Character,利用策略模式進行攻擊能力的策略選擇。

https://ithelp.ithome.com.tw/upload/images/20190925/20120614wp2iXaFmJp.png

以上就是利用策略模式狀態下,父類別 Character 藉由一個參考點(也就是父類別的成員變數 attackRef)連結到 Attack 介面。藉由 Attack 介面可以實踐出各種不同的攻擊策略,在這裡的範例是分別實踐出 MeleeAttack 以及 MagicAttack

子類別經由父類別進行繼承後 —— 由於父類別有宣告與 Attack 介面連結的參考點,子類別可以選擇其中一種 Attack 介面宣告出的策略,進行選擇攻擊的演算法,不需另外再寫一個 attack 成員方法去覆蓋父類別的 attack 方法呢。

本篇的範例程式碼可以參考這個連結

策略的切換 Switching Strategies

由上一篇可以切換演算法部分進行延伸。

假若角色可能會出現不同的攻擊方式,譬如:Swordsman 除了會 MeleeAttack (直接攻擊)外,可能還會有 StabAttack (刺擊)這種攻擊法。

首先可以藉由 Attack 介面另訂一個新攻擊策略。

https://ithelp.ithome.com.tw/upload/images/20190926/20120614iKkvbGz0p9.png

這裡就出現一個問題了:Swordsman 預設的攻擊模式(也就是它的策略)是 MeleeAttack —— 不過還記得筆者在前一篇文章有貼過描述策略模式的一句話嗎?

Changing algorithm during runtime.

根據上面那句話的意思:如何在 runtime 期間進行切換策略(演算法)的動作呢?

換句話說,筆者不希望在建構 Swordsman 角色的時候更動它原本的設定 —— 也就是 MeleeAttack,但是要如何在程式碼跑的過程進行更換攻擊策略的動作。

其實非常簡單,可以在父類別宣告一個 switchAttackStrategy 成員方法 —— 負責進行策略的切換;這裡的範例指的是進行攻擊方式的切換。以下是簡單的實踐:

https://ithelp.ithome.com.tw/upload/images/20190926/20120614f28N0g2wLW.png

筆者寫一段簡單的程式碼進行測試。(如圖一)

https://ithelp.ithome.com.tw/upload/images/20190926/201206145QGKgrwpxO.png
圖一:我們可以更換角色的攻擊策略囉!

如果沒有應用策略模式,想要更換策略的話,可能得設定一個 Flag 負責記錄該角色目前的攻擊方式,然後再進行 if...else... 這一類的判斷,又會回到原來那一大串判斷敘述地獄了。

然而,經由策略模式,我們可以捨棄掉多重判斷敘述的同時,也可以達到類別與介面被重複使用的功能 —— 以 Warlock 為例,它也繼承了父類別 Character,代表 Warlock 類型的角色也可以切換不同的策略呢!

本篇唯一重點. 策略模式的優勢

策略模式最大的威力在於:新增各種策略在子類別時,不需要覆蓋父類別的實踐,就可以間接切換策略達到目的

此外,新增的策略也可以被重複加以利用而不會影響到子類別的功能實踐。

這個優勢符合了這句話的形容 —— “Changing algorithm during runtime.

運用策略模式的優勢

我們緊接著使用策略模式的優勢,設計出更多 RPG 角色可能會有的功能。以下為了更熟悉策略模式的概念,筆者會繼續帶領讀者一遍又一遍地熟悉這個設計模式的實踐流程。

設計角色裝備武器的功能 Weapon Equipment

常理的 RPG 遊戲設計時,通常角色的攻擊方式是會根據武器特性而決定的,除非你是使出技能之類的機制才會轉換攻擊演算法。

以下來試試看能不能實踐出這個功能:設計武器 Weapon 介面,其中延伸出各種不同的武器,讓角色有能力去裝備這些東西;並且角色不需要進行初始化攻擊策略,而是由這些武器去自動鎖定攻擊策略

筆者就按照前一篇講過的步驟嚴格執行。

步驟 1. 策略的介面綁定與宣告

首先,筆者建立 weapons 資料夾,並且新增 weapons/Weapon.ts 檔案,負責定義每個武器必須實作的特性:

https://ithelp.ithome.com.tw/upload/images/20190926/20120614O20a8PHN22.png

Weapon 介面有幾個規格:

  • name 為武器名稱,為 readonly 模式
  • availableRoles 控制的是武器可以被哪個職業裝備
  • attackStrategy 則是武器綁定的是哪一種基礎攻擊策略

另外,我們分別新增三種不同的武器:BasicSwordBasicWand 以及 Dagger

https://ithelp.ithome.com.tw/upload/images/20190926/20120614g5jcibEFRo.png

https://ithelp.ithome.com.tw/upload/images/20190926/20120614mKUGWEzNwO.png

https://ithelp.ithome.com.tw/upload/images/20190926/201206141Sc5CmRiOO.png

步驟 2. 父類別建立策略參考點

接下來是在父類別進行參考點的建立。不過這一次跟前一篇不同的是 —— 原本可以讓角色自由切換攻擊策略 Attack,這一次希望達到的目標則是:角色裝備武器的同時,攻擊策略根據 WeaponattackStrategy 自動進行綁定。

https://ithelp.ithome.com.tw/upload/images/20190926/201206149BAoMJkgWu.png

步驟 3. 藉由參考點進行功能傳遞的動作

主要是角色負責進行攻擊 attack 的動作,而 attack 早就在前一篇被實現了。

https://ithelp.ithome.com.tw/upload/images/20190926/20120614HINHD1jKmS.png

不過,本範例是需要藉由更換武器 Weapon,因此必須設計的主要功能是 equip 方法 —— 負責接收 Weapon 類型的物件作為參數,藉以調整攻擊策略。

https://ithelp.ithome.com.tw/upload/images/20190926/20120614FbEMTp4kcb.png

以上的程式碼,也利用了 availableRoles 進行武器能否被裝備在角色身上的檢測。

步驟 4. 子類別可以選擇策略

本範例寫的策略,自然而然是更換武器的概念 —— 我們可以在子類別初始化武器的動作。

不過這裡要注意的是 —— 原本選擇攻擊的策略已經被置換成藉由選擇武器就會自動進行攻擊策略的綁定,所以對於 SwordsmanWarlock 分別實踐之結果如下:

https://ithelp.ithome.com.tw/upload/images/20190926/20120614pO1y59JP0L.png

https://ithelp.ithome.com.tw/upload/images/20190926/20120614tL6gzN3jrA.png

以上功能已經完成囉!我們開始進行驗收的動作。(以下程式碼編譯並執行結果如圖二)

https://ithelp.ithome.com.tw/upload/images/20190926/20120614SQMvFHodCm.png

https://ithelp.ithome.com.tw/upload/images/20190926/20120614SaAO28z1gq.png
圖二:除了可以切換掉武器外,也發現切換錯武器也會自動拋出例外呢!

圖三就是目前的 CharacterAttackWeapon 之間的關係圖。

https://ithelp.ithome.com.tw/upload/images/20190926/20120614atn7rU8kod.png
圖三:類別和介面有連結,使得類別跟不同介面綁定下的策略進行彈性的互換操作

不過,請不要被以上的實踐被死死地綁住 —— 設計一個系統的方式有很多種,重點在於要如何設計好物件跟策略的互動關係。

譬如,筆者也可以選擇不要讓角色 CharacterAttack 攻擊策略有連結,而必須讓角色藉由與武器 Weapon 的連結進行攻擊 attack 的動作!不過筆者想像中的架構結果可能會變成這樣。(如圖四)

https://ithelp.ithome.com.tw/upload/images/20190926/20120614NZmBw1QHFJ.png

必須藉由選擇的 Weapon 再去連結各種不同的攻擊策略,這樣做的好處是 —— 以 Warlock 為例,它除了可以選擇用匕首 Dagger 進行 MagicAttack 魔法攻擊外,也可以選擇 StabAttack 單純物理性地刺擊。

所以要讓策略模式的發揮方式非常多種,最終完全於你想讓系統能夠出現什麼樣的行為

小結

今天大致上再讓讀者更熟悉策略模式的好處跟彈性!最重要的就是:策略可以隨時隨地被切換、策略也可以同時被不同的類別重複使用。(前提是要正確地使用策略模式 —— 筆者知道這是廢話 XD)

下一篇,筆者要補充類別還沒講完的部分,那就是 —— 抽象類別


上一篇
Day 26. 機動藍圖・策略模式 X 選擇策略 - Strategy Pattern Using TypeScript. I
下一篇
Day 28. 機動藍圖・抽象類別 X 藍圖基底 - TypeScript Abstract Class
系列文
讓 TypeScript 成為你全端開發的 ACE!51

2 則留言

0
ch_lute
iT邦新手 5 級 ‧ 2020-01-06 16:27:17

請問AttackRef為什麼要用private?

通常設計成 private 成員的狀況是 -- 我們不希望使用者使用該成員喔

如果 attackRef 使用 public 就代表外面的人可能可以藉由竄改 attackRef 導致程序發生無法預知的狀況

再加上,如果我們儘量封裝好物件,避免洩露越多東西,有幾個好處:

  1. 除了剛剛講得避免使用者亂竄改外
  2. 可以使用的功能數量越少,代表介面相對也會比較簡單,複雜度比較低
  3. 測試會很好寫,因為你只需要測試 public 成員就好
ch_lute iT邦新手 5 級 ‧ 2020-02-11 17:23:56 檢舉

感謝解答。當時單純覺得如果用public可以少打很多行程式。

0
danny900714
iT邦新手 5 級 ‧ 2020-05-22 00:07:00

請問文章中的 UML 關係圖是用什麼軟體做的呢,看起來好漂亮~~

用 Illustrator 慢慢刻的 XD

(抱歉有點晚回覆 XD,最近作者太忙)

我要留言

立即登入留言