設計模式 Strategy

當面對很多不同境境時採取不同的策略,例如:根據距離決定使用何種交通工具、不同的運送快遞有不同的金額計算方式、甚至是複雜點的表單驗證器、回合制戰鬥設計等,這個時候可以使用策略模式來設計物件。

設計模式 Strategy
Photo by JESHOOTS.COM / Unsplash

筆記學習設計模式 Strategy (策略模式)的一些想法以及簡單的 sample code 紀錄一下學習中的想法。

使用情境

當面對很多不同境境時採取不同的策略,例如:根據距離決定使用何種交通工具、不同的運送快遞有不同的金額計算方式、甚至是複雜點的表單驗證器、回合制戰鬥設計等

特點

  • 封裝不同的演算法,使得關注點分離
  • 容易擴展,只要寫新的 strategy 不必修改原物件
  • 必須暸解各個 strategy 才會知道該使用哪個策略

範例

以下使用 JavaScript 當作範例,實作一個將物件轉換為 xml 或是 json 格式的範例 code
思考其實就是實作兩個 strategy 分別做自己該做的事情。

const data = {
  name: 'rj',
  msg: 'show me the money'
}

// 其實可以用迭代的方式去組會比較輕鬆點
const xmlStrategy = {
  send: function (data) {
    return `
    <xml>
      <name>${data.name}</name>
      <msg>${data.msg}</msg>
    </xml>
    `
  }
}

const jsonStrategy = {
  send: function (data) {
    return JSON.stringify(data)
  }
}

class Sender {
  constructor(strategy) {
    this.strategy = strategy
  }

  send(data) {
    return this.strategy.send(data)
  }
}

const sender = new Sender(xmlStrategy) // 可以替換 strategy
console.log(sender.send(data))

上面這個範例可能沒那麼有感覺,那麼我們將情境換成 RPG 回合制戰鬥好了

function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
class Hero {
  constructor(name, skill) {
    this.name = name;
    this.hp = 100;
    this.mp = 100;
    this.skill = skill
  }

  costHp(hp) {
    this.hp -= hp;
  }

  costMp(mp) {
    this.mp -= mp;
  }

  attack(target) {
    this.costMp(this.skill.mp);
    const injury = this.skill.attack(this, target)
    console.log(`${this.name} attack ${target.name} 造成了 ${injury} 點傷害`);
  }

  getHp() {
    console.log(this.hp);
  }

  isAlive() {
    return this.hp > 0;
  }
}

class Skill {
  constructor(name, costMp) {
    this.name = name;
    this.costMp = costMp;
  }

  attack(source, target) {
    source.costMp(this.costMp);
    target.costHp(this.attackPower);
    return this.attackPower
  }
}

class Fireball extends Skill {
  constructor() {
    super('火球術', 10);
    this.attackPower = 50;
  }
}

class Watergun extends Skill {
  constructor() {
    super('水槍術', 5);
    this.attackPower = 30;
  }
}

(function () {
  rj = new Hero('RJ', new Fireball);
  gg = new Hero('GG', new Watergun);

  while (rj.isAlive() && gg.isAlive()) {
    rj.attack(gg);
    gg.attack(rj);
    rj.getHp();
    gg.getHp();
  }
})()

這樣是不是有感覺了一點,像是各個 skill 都是一個 strategy 如果我今天要再新增一個 skill 那麼只要在建立一個 class 然後 extend skill 就可以了。這樣計算傷害的演算法只需要封裝起來自己維護就好。

參考資料

https://www.dofactory.com/javascript/design-patterns/strategy
https://ithelp.ithome.com.tw/articles/10202419
https://www.youtube.com/watch?v=IkG_KuMpQRM