iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 27
4
Modern Web

在 React 生態圈內打滾的一年 feat. TypeScript系列 第 27

Day26 | 精選設計模式實戰,打通 interface 及 class 的運用觀念

前言

本篇會用前兩篇提到的 Class 與 Interface 玩轉設計模式中的「策略模式」,希望能透過實際的運用讓大家更了解兩者的運用觀念,只要能夠好好運用,就會發現許多設計模式都很有趣,非常值得一學。


前置準備

  1. 文中的專案會以 Day25 的專案架構繼續講解,如果未跟到前一天的進度,可以從 GitHub 上 Clone 下來。
  2. 一顆擁有學習熱忱的心。

Strategy Pattern 策略模式

在開始前簡單介紹一下什麼是策略模式,

Strategy Pattern 主要在處理各種既相同,但做起來又不太一樣的行為。

將行為抽離後,視情況隨意組合切換。

設計情境

上方提到既相同又不太一樣的行爲似乎有點矛盾,但是仔細想想,以遊戲中的「攻擊」這個動作好了,劍士、弓箭手、魔法師都會攻擊,但是它們攻擊的方法都不一樣對吧?

於是相同的行為就是「攻擊」,於是我們將「攻擊」給抽出來,設計成 Interface ,並實作出幾種 Class ,代表各種「達成行為的方式」,以下程式碼會在目錄中建立 Strategy_Pattern/index.ts, 並實作於其中:

interface IAttackBehavior {
  attack: () => string;
}

class Cut implements IAttackBehavior {
  public attack(): string {
    return '砍劈攻擊';
  }
}

class Magic implements IAttackBehavior {
  public attack(): string {
    return '魔法攻擊';
  }
}

class Shooting implements IAttackBehavior {
  public attack(): string {
    return '射擊攻擊';
  }
}

現在我們有了幾種攻擊的行為,接著創建一個有攻擊行為的人:

class Person {
  public name: string;

  constructor(name: string) {
    this.name = name;
  }

  public attack(): void {

  }
}

該怎麼做才有辦法讓 Person 能夠不寫死,並隨情況改變攻擊方式呢?

答案很簡單,只需要應用 Interface 就行了,我們可以在一開始創建新角色的時候,將該角色的攻擊模式和 name 一起傳入 constructor 中,並將攻擊模式寫入該角色的 Property,例如:

class Person {
  public name: string;
  public attackBehavior: IAttackBehavior;

  constructor(name: string, attackBehavior: IAttackBehavior) {
    this.name = name;
    this.attackBehavior = attackBehavior;
  }

  public attack(): void {

  }
}

接下來要怎麼做大家應該很明白了!既然攻擊模式在 constructor 中已經寫入型別為 Interface IAttackBehaviorthis.attackBehavior 中了,那便直接對 Interface 做操作,也就是用 this.attackBehavior 執行 attack 的 Method 就好:

class Person {
  /* 其餘省略 */
  public attack(): void {
    // 從 this.attackBehavior 中執行 attack
    console.log(`${this.name}發動:${this.attackBehavior.attack()}`);
  }
}

這麼一來,在一開始創建 Person 的時候,就能先為角色指定一種攻擊模式:

const character = new Person('神 Q 超人', new Cut());
character.attack(); // 神 Q 超人發動:砍劈攻擊

但是這麼做又有什麼彈性可言?還不是只有一種攻擊模式嗎?不如直接把執行寫在裡面,何必多寫那麼多行為的 Interface 及 Class?別急,厲害的來了,記得 Person 裡的 attackBehavior 的型別是 IAttackBehavior 嗎?也就是說所有的攻擊模式都可以放到這個 Property 中,因為只要是攻擊模式都會擁有 IAttackBehavior 接口,至於怎麼替換?就替 Person 增加一個替換 attackBehavior 的 Property Method 吧:

class Person {
  /* 其餘省略 */
  public changeAttackBehavior(attackBehavior: IAttackBehavior) {
    this.attackBehavior = attackBehavior;
  }
}

如此一來,被建立出來的角色就能以 changeAttackBehavior 來變更自身的攻擊模式了:

const character = new Person('神 Q 超人', new Cut());
character.attack(); // 神 Q 超人發動:砍劈攻擊

character.changeAttackBehavior(new Magic());
character.attack(); // 神 Q 超人發動:魔法攻擊

character.changeAttackBehavior(new Shooting());
character.attack(); // 神 Q 超人發動:射擊攻擊

這時候如果要再多個攻擊模式,只需要再添加一個實作 IAttackBehavior 的 Class 就好,其餘的程式碼都不需要改變,不只完美的履行 開放封閉原則,而且 Person 只對 Interface 做操作,根本就不在乎是誰實作了它,這部分也把「攻擊」的邏輯給封裝起來了。

本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)


結尾

本篇文章利用了 Strategy Pattern 策略模式來解說 Class 與 Interface,希望能讓人感受到它們的魅力或是對設計有不同的想法,如果看完有任何感想都歡迎告訴我!對設計模式有興趣的話,版上也有許多關於設計模式的文章,雖然以前只用 JavaScript 時就像圈外人,但現在就能用 TypeScript 玩轉一波了!

如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!


上一篇
Day25 | 善用 interface 抽象概念,為 Class 找到出路
下一篇
Day27 | 最強聯名款 TSX 上市-Babel、Webpack、Jest 篇
系列文
在 React 生態圈內打滾的一年 feat. TypeScript31

尚未有邦友留言

立即登入留言