iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
Software Development

也該是時候學學 Design Pattern 了系列 第 19

Day 19: Behavioral patterns - Command

目的

將行為拆成請求與執行兩個區塊,請求的部分獨自封裝成物件,執行的部分則交由專門負責執行的物件。
負責執行的物件除了儲存等待執行的請求,還可以在執行前取消特定請求。

說明

想像一個情況,使用者可以很簡單地說出要執行的行為,負責執行的程式可以迅速反應,轉換到設計上,單個要執行的行為,經常包裝成物件,物件內有執行的細節。

現在情況改變,有許多的行為可以執行,那要建立相同數量的物件,數量越多,越不容易管理,因為分散各地。
此外,如果需要一個歷史清單追蹤要執行的行為,以及取消特定行為呢?

單純的「傳球、接球」模式無法做到。

為了實踐這兩個新需求,可以將執行的細節封裝在特定物件內,原本的包裝行為的物件簡化成呼叫特定物件內的對應方法。

實踐的作法是:

  1. 建立特定物件(Receiver),負責各個行為的執行細節。
  2. 建立行為的虛擬層親代,開規格要求所有行為都要有「執行」方法以及「與特定物件的連結」。
  3. 建立行為子代,在「執行」呼叫特定物件上的對應方法
  4. 建立使用者對外窗口(Invoker),負責接受、儲存、取消使用者要求的行為,並在確定後執行所有行為。

以下範例以「模擬世紀帝國二的基本操作」為核心製作。

UML 圖

Command Pattern UML Diagram

使用 Java 實作

單位物件:VillagerPaladinHouseCastle

public class Villager {
    private int healthPoints;
    private Headquarters headquarters;

    public Villager(Headquarters headquarters) {
        healthPoints = 25;
        this.headquarters = headquarters;
    }

    public void gotDamage(int damagePoints) {
        healthPoints -= damagePoints;

        if (healthPoints < 0) {
            headquarters.removeVillager();
        }
    }
}

public class Paladin {
    private int healthPoints;
    private Headquarters headquarters;

    public Paladin(Headquarters headquarters) {
        healthPoints = 160;
        this.headquarters = headquarters;
    }

    public void gotDamage(int damagePoints) {
        healthPoints -= damagePoints;

        if (healthPoints < 0) {
            headquarters.removePaladin();
        }
    }
}

public class House {
    private int healthPoints;
    private Headquarters headquarters;

    public House(Headquarters headquarters) {
        healthPoints = 550;
        this.headquarters = headquarters;
    }

    public void gotDamage(int damagePoints) {
        healthPoints -= damagePoints;

        if (healthPoints < 0) {
            headquarters.removeHouse();
        }
    }
}

public class Castle {
    private int healthPoints;
    private Headquarters headquarters;

    public Castle(Headquarters headquarters) {
        healthPoints = 4800;
        this.headquarters = headquarters;
    }

    public void gotDamage(int damagePoints) {
        healthPoints -= damagePoints;

        if (healthPoints < 0) {
            headquarters.removeCastle();
        }
    }
}

特定物件(Receiver)

public class Headquarters {
    private int woodCounts;
    private int foodCounts;
    private int goldCounts;
    private int stoneCounts;
    private ArrayList<Villager> villagerList = new ArrayList<>();
    private ArrayList<Paladin> paladinList = new ArrayList<>();
    private ArrayList<House> houseList = new ArrayList<>();
    private ArrayList<Castle> castleList = new ArrayList<>();

    public Headquarters() {
        woodCounts = 100;
        foodCounts = 100;
        goldCounts = 50;
        stoneCounts = 50;
    }

    public void createVillager() {
        if (foodCounts < 50) {
            System.out.println("食物不足,無法生產村民\n");
        } else {
            System.out.println("食物充足,已生產村民\n");
            villagerList.add(new Villager(this));
            foodCounts -= 50;
        }
    }

    public void removeVillager() {
        if (villagerList.isEmpty()) {
            System.out.println("沒有村民可以刪除");
        } else {
            villagerList.remove(0);
            System.out.println("村民已刪除");
        }
    }

    public void createPaladin() {
        if (foodCounts < 60) {
            System.out.println("食物不足,無法生產遊俠\n");
        } else if (goldCounts < 75) {
            System.out.println("黃金不足,無法生產遊俠\n");
        } else {
            System.out.println("食物充足,已生產遊俠\n");
            paladinList.add(new Paladin(this));
            foodCounts -= 60;
            goldCounts -= 75;
        }
    }

    public void removePaladin() {
        if (paladinList.isEmpty()) {
            System.out.println("沒有遊俠可以刪除");
        } else {
            paladinList.remove(0);
            System.out.println("遊俠已刪除");
        }
    }

    public void createHouse() {
        if (woodCounts < 25) {
            System.out.println("木頭不足,無法建造居住房舍\n");
        } else {
            System.out.println("木頭充足,已建造居住房舍\n");
            houseList.add(new House(this));
            woodCounts -= 25;
        }
    }

    public void removeHouse() {
        if (houseList.isEmpty()) {
            System.out.println("沒有居住房舍可以刪除");
        } else {
            houseList.remove(0);
            System.out.println("居住房舍已刪除");
        }
    }

    public void createCastle() {
        if (stoneCounts < 650) {
            System.out.println("石頭不足,無法建造城堡\n");
        } else {
            System.out.println("石頭充足,已建造城堡\n");
            castleList.add(new Castle(this));
            stoneCounts -= 600;
        }
    }

    public void removeCastle() {
        if (castleList.isEmpty()) {
            System.out.println("沒有城堡可以刪除");
        } else {
            castleList.remove(0);
            System.out.println("城堡已刪除");
        }
    }

    public void gatherWood() {
        if (villagerList.isEmpty()) {
            System.out.println("沒有村民可用\n");
        } else {
            System.out.println("村民數量足夠,開始砍樹\n");
            woodCounts += 100;
        }
    }

    public void gatherFood() {
        if (villagerList.isEmpty()) {
            System.out.println("沒有村民可用\n");
        } else {
            System.out.println("村民數量足夠,開始採集食物\n");
            foodCounts += 100;
        }
    }

    public void gatherGold() {
        if (villagerList.isEmpty()) {
            System.out.println("沒有村民可用\n");
        } else {
            System.out.println("村民數量足夠,開始挖金礦\n");
            goldCounts += 100;
        }
    }

    public void gatherStone() {
        if (villagerList.isEmpty()) {
            System.out.println("沒有村民可用\n");
        } else {
            System.out.println("村民數量足夠,開始挖石礦\n");
            stoneCounts += 100;
        }
    }

    public void showResources() {
        System.out.println("---當前資源---");
        System.out.println("木頭: " + woodCounts);
        System.out.println("食物: " + foodCounts);
        System.out.println("黃金: " + goldCounts);
        System.out.println("石頭: " + stoneCounts);
        System.out.println("---當前建築數量---");
        System.out.println("居住房舍: " + houseList.size());
        System.out.println("城堡: " + castleList.size());
        System.out.println("---當前單位數量---");
        System.out.println("村民: " + villagerList.size());
        System.out.println("遊俠: " + paladinList.size());
        System.out.println("---完畢---\n");
    }
}

行為的虛擬層親代:Command

public abstract class Command {
    protected Headquarters headquarters;

    protected Command(Headquarters headquarters) {
        this.headquarters = headquarters;
    }

    public abstract void executeCommand();

    public abstract String getName();
}

行為子代:CreateVillagerCommandRemoveVillagerCommandCreatePaladinCommandRemovePaladinCommandCreateHouseCommandRemoveHouseCommandCreateCastleCommandRemoveCastleCommandGatherFoodCommandGatherGoldCommandGatherStoneCommandGatherWoodCommandShowResourcesCommand(Command 物件)

public class CreateVillagerCommand extends Command {
    private String name = "生產村民";

    public CreateVillagerCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.createVillager();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class RemoveVillagerCommand extends Command {
    private String name = "刪除村民";

    public RemoveVillagerCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.removeVillager();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class CreatePaladinCommand extends Command {
    private String name = "生產遊俠";

    public CreatePaladinCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.createPaladin();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class RemovePaladinCommand extends Command {
    private String name = "刪除遊俠";

    public RemovePaladinCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.removePaladin();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class CreateHouseCommand extends Command {
    private String name = "建造居住房舍";

    public CreateHouseCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.createHouse();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class RemoveHouseCommand extends Command {
    private String name = "刪除居住房舍";

    public RemoveHouseCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.removeHouse();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class CreateCastleCommand extends Command {
    private String name = "建造城堡";

    public CreateCastleCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.createCastle();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class RemoveCastleCommand extends Command {
    private String name = "刪除城堡";

    public RemoveCastleCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.removeCastle();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class GatherFoodCommand extends Command {
    private String name = "採集食物";

    public GatherFoodCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.gatherFood();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class GatherGoldCommand extends Command {
    private String name = "挖金礦";

    public GatherGoldCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.gatherGold();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class GatherStoneCommand extends Command {
    private String name = "挖石礦";

    public GatherStoneCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.gatherStone();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class GatherWoodCommand extends Command {
    private String name = "砍樹";

    public GatherWoodCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.gatherWood();
    }

    @Override
    public String getName() {
        return name;
    }
}

public class ShowResourcesCommand extends Command {
    private String name = "顯示資源、建築與單位";

    public ShowResourcesCommand(Headquarters headquarters) {
        super(headquarters);
    }

    @Override
    public void executeCommand() {
        headquarters.showResources();
    }

    @Override
    public String getName() {
        return name;
    }
}

對外窗口(Invoker):GUI

public class GUI {
    private ArrayList<Command> orders;

    public GUI() {
        this.orders = new ArrayList<>();
    }

    public void receivePlayerCommand(Command command) {
        orders.add(command);
        System.out.println("接收指令: " + command.getName() + " 時間:" + new Date());
    }

    public void cancelPlayerCommand(Command command) {
        orders.remove(command);
        System.out.println("取消指令: " + command.getName() + " 時間:" + new Date());
    }

    public void executePlayerCommands() {
        for (Command command : orders) {
            command.executeCommand();
        }
    }
}

測試,玩家嘗試建造房屋、城堡,生產村民、遊俠,採集木頭、食物、黃金、石頭,最後顯示資源與單位數量:RTSCommandSample

public class RTSCommandSample {
    public static void main(String[] args) {
        Headquarters headquarters = new Headquarters();
        Command createVillagerCommand = new CreateVillagerCommand(headquarters);
        Command createPaladinCommand = new CreatePaladinCommand(headquarters);
        Command createHouseCommand = new CreateHouseCommand(headquarters);
        Command createCastleCommand = new CreateCastleCommand(headquarters);
        Command gatherWoodCommand = new GatherWoodCommand(headquarters);
        Command gatherFoodCommand = new GatherFoodCommand(headquarters);
        Command gatherGoldCommand = new GatherGoldCommand(headquarters);
        Command gatherStoneCommand = new GatherStoneCommand(headquarters);
        Command showResourcesCommand = new ShowResourcesCommand(headquarters);
        GUI gui = new GUI();

        gui.receivePlayerCommand(createVillagerCommand);

        gui.receivePlayerCommand(createPaladinCommand);
        gui.receivePlayerCommand(gatherFoodCommand);
        gui.receivePlayerCommand(gatherFoodCommand);
        gui.receivePlayerCommand(gatherGoldCommand);
        gui.receivePlayerCommand(gatherGoldCommand);
        gui.receivePlayerCommand(createPaladinCommand);

        gui.receivePlayerCommand(createHouseCommand);
        gui.receivePlayerCommand(createHouseCommand);
        gui.receivePlayerCommand(createHouseCommand);
        gui.receivePlayerCommand(createHouseCommand);
        gui.receivePlayerCommand(createHouseCommand);
        gui.receivePlayerCommand(gatherWoodCommand);
        gui.receivePlayerCommand(createHouseCommand);
        gui.cancelPlayerCommand(createHouseCommand);
        gui.cancelPlayerCommand(createHouseCommand);

        gui.receivePlayerCommand(createCastleCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(gatherStoneCommand);
        gui.cancelPlayerCommand(gatherStoneCommand);
        gui.receivePlayerCommand(createCastleCommand);

        gui.receivePlayerCommand(showResourcesCommand);

        gui.executePlayerCommands();
    }
}

使用 JavaScript 實作

單位物件:VillagerPaladinHouseCastle

class Villager {
  /** @param {Headquarters} headquarters */
  constructor(headquarters) {
    this.healthPoints = 25;
    this.headquarters = headquarters;
  }

  /** @param {number} damagePoints */
  /** @param {number} damagePoints */
  gotDamage(damagePoints) {
    this.healthPoints -= damagePoints;

    if (this.healthPoints < 0) {
      this.headquarters.removeVillager();
    }
  }
}

class Paladin {
  /** @param {Headquarters} headquarters */
  constructor(headquarters) {
    this.healthPoints = 160;
    this.headquarters = headquarters;
  }

  /** @param {number} damagePoints */
  gotDamage(damagePoints) {
    this.healthPoints -= damagePoints;

    if (this.healthPoints < 0) {
      this.headquarters.removePaladin();
    }
  }
}

class House {
  /** @param {Headquarters} headquarters */
  constructor(headquarters) {
    this.healthPoints = 550;
    this.headquarters = headquarters;
  }

  /** @param {number} damagePoints */
  gotDamage(damagePoints) {
    this.healthPoints -= damagePoints;

    if (this.healthPoints < 0) {
      this.headquarters.removeHouse();
    }
  }
}

class Castle {
  /** @param {Headquarters} headquarters */
  constructor(headquarters) {
    this.healthPoints = 4800;
    this.headquarters = headquarters;
  }

  /** @param {number} damagePoints */
  gotDamage(damagePoints) {
    this.healthPoints -= damagePoints;

    if (this.healthPoints < 0) {
      this.headquarters.removeCastle();
    }
  }
}

特定物件(Receiver)

class Headquarters {
  constructor() {
    this.woodCounts = 100;
    this.foodCounts = 100;
    this.goldCounts = 50;
    this.stoneCounts = 50;
    /** @type {Villager[]} */
    this.villagerList = [];
    /** @type {Paladin[]} */
    this.paladinList = [];
    /** @type {House[]} */
    this.houseList = [];
    /** @type {Castle[]} */
    this.castleList = [];
  }

  createVillager() {
    if (this.foodCounts < 50) {
      console.log("食物不足,無法生產村民\n");
    } else {
      console.log("食物充足,已生產村民\n");
      this.villagerList.push(new Villager(this));
      this.foodCounts -= 50;
    }
  }

  removeVillager() {
    if (this.villagerList.length === 0) {
      console.log("沒有村民可以刪除");
    } else {
      this.villagerList.shift();
      console.log("村民已刪除");
    }
  }

  createPaladin() {
    if (this.foodCounts < 60) {
      console.log("食物不足,無法生產遊俠\n");
    } else if (this.goldCounts < 75) {
      console.log("黃金不足,無法生產遊俠\n");
    } else {
      console.log("食物充足,已生產遊俠\n");
      this.paladinList.push(new Paladin(this));
      this.foodCounts -= 60;
      this.goldCounts -= 75;
    }
  }

  removePaladin() {
    if (this.paladinList.length === 0) {
      console.log("沒有遊俠可以刪除");
    } else {
      this.paladinList.shift();
      console.log("遊俠已刪除");
    }
  }

  createHouse() {
    if (this.woodCounts < 25) {
      console.log("木頭不足,無法建造居住房舍\n");
    } else {
      console.log("木頭充足,已建造居住房舍\n");
      this.houseList.push(new House(this));
      this.woodCounts -= 25;
    }
  }

  removeHouse() {
    if (houseList.length === 0) {
      console.log("沒有居住房舍可以刪除");
    } else {
      this.houseList.shift();
      console.log("居住房舍已刪除");
    }
  }

  createCastle() {
    if (this.stoneCounts < 650) {
      console.log("石頭不足,無法建造城堡\n");
    } else {
      console.log("石頭充足,已建造城堡\n");
      this.castleList.push(new Castle(this));
      this.stoneCounts -= 600;
    }
  }

  removeCastle() {
    if (this.castleList.length === 0) {
      console.log("沒有城堡可以刪除");
    } else {
      this.castleList.shift();
      console.log("城堡已刪除");
    }
  }

  gatherWood() {
    if (this.villagerList.length === 0) {
      console.log("沒有村民可用\n");
    } else {
      console.log("村民數量足夠,開始砍樹\n");
      this.woodCounts += 100;
    }
  }

  gatherFood() {
    if (this.villagerList.length === 0) {
      console.log("沒有村民可用\n");
    } else {
      console.log("村民數量足夠,開始採集食物\n");
      this.foodCounts += 100;
    }
  }

  gatherGold() {
    if (this.villagerList.length === 0) {
      console.log("沒有村民可用\n");
    } else {
      console.log("村民數量足夠,開始挖金礦\n");
      this.goldCounts += 100;
    }
  }

  gatherStone() {
    if (this.villagerList.length === 0) {
      console.log("沒有村民可用\n");
    } else {
      console.log("村民數量足夠,開始挖石礦\n");
      this.stoneCounts += 100;
    }
  }

  showResources() {
    console.log("---當前資源---");
    console.log("木頭: " + this.woodCounts);
    console.log("食物: " + this.foodCounts);
    console.log("黃金: " + this.goldCounts);
    console.log("石頭: " + this.stoneCounts);
    console.log("---當前建築數量---");
    console.log("居住房舍: " + this.houseList.length);
    console.log("城堡: " + this.castleList.length);
    console.log("---當前單位數量---");
    console.log("村民: " + this.villagerList.length);
    console.log("遊俠: " + this.paladinList.length);
    console.log("---完畢---\n");
  }
}

行為的虛擬層親代:Command

/** @abstract */
class Command {
  /** @param {Headquarters} headquarters  */
  constructor(headquarters) {
    this.headquarters = headquarters;
  }

  /** @abstract */
  executeCommand() { return; }

  /** @abstract */
  getName() { return; }
}

行為子代:CreateVillagerCommandRemoveVillagerCommandCreatePaladinCommandRemovePaladinCommandCreateHouseCommandRemoveHouseCommandCreateCastleCommandRemoveCastleCommandGatherFoodCommandGatherGoldCommandGatherStoneCommandGatherWoodCommandShowResourcesCommand(Command 物件)

class CreateVillagerCommand extends Command {
  /** @param {Headquarters} headquarters */
  constructor(headquarters) {
    super(headquarters);
    this.name = "生產村民";
  }

  /** @override */
  executeCommand() {
    this.headquarters.createVillager();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class RemoveVillagerCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "刪除村民";
  }

  /** @override */
  executeCommand() {
    this.headquarters.removeVillager();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class CreatePaladinCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "生產遊俠";
  }

  /** @override */
  executeCommand() {
    this.headquarters.createPaladin();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class RemovePaladinCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "刪除遊俠";
  }

  /** @override */
  executeCommand() {
    this.headquarters.removePaladin();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class CreateHouseCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "建造居住房舍";
  }

  /** @override */
  executeCommand() {
    this.headquarters.createHouse();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class RemoveHouseCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "刪除居住房舍";
  }

  /** @override */
  executeCommand() {
    this.headquarters.removeHouse();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class CreateCastleCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "建造城堡";
  }

  /** @override */
  executeCommand() {
    this.headquarters.createCastle();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class RemoveCastleCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "刪除城堡";
  }

  /** @override */
  executeCommand() {
    this.headquarters.removeCastle();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class GatherFoodCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "採集食物";
  }

  /** @override */
  executeCommand() {
    this.headquarters.gatherFood();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class GatherGoldCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "挖金礦";
  }

  /** @override */
  executeCommand() {
    this.headquarters.gatherGold();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class GatherStoneCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "挖石礦";
  }

  /** @override */
  executeCommand() {
    this.headquarters.gatherStone();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class GatherWoodCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "砍樹";
  }

  /** @override */
  executeCommand() {
    this.headquarters.gatherWood();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

class ShowResourcesCommand extends Command {
  constructor(headquarters) {
    super(headquarters);
    this.name = "顯示資源、建築與單位";
  }

  /** @override */
  executeCommand() {
    this.headquarters.showResources();
  }

  /** @override */
  getName() {
    return this.name;
  }
}

對外窗口(Invoker):GUI

class GUI {
  constructor() {
    /** @type {Command[]} */
    this.orders = [];
  }

  receivePlayerCommand(command) {
    this.orders.push(command);
    console.log("接收指令: " + command.getName() + " 時間:" + new Date().toISOString());
  }

  cancelPlayerCommand(command) {
    this.orders.filter((item) => item !== command);
    let index = -1;

    for (let i = 0; i < this.orders.length; i++) {
      const item = this.orders[i];

      if (item.getName() === command.getName()) {
        index = i;
        break;
      }
    }

    if (index >= 0) {
      this.orders.splice(index, 1);
    }

    console.log("JSON.stringify(this.orders) " + JSON.stringify(this.orders))
    console.log("取消指令: " + command.getName() + " 時間:" + new Date().toISOString());
  }

  executePlayerCommands() {
    for (const command of this.orders) {
      command.executeCommand();
    }
  }
}

測試,玩家嘗試建造房屋、城堡,生產村民、遊俠,採集木頭、食物、黃金、石頭,最後顯示資源與單位數量:rtsCommandSample

const rtsCommandSample = () => {
  const headquarters = new Headquarters();
  const createVillagerCommand = new CreateVillagerCommand(headquarters);
  const createPaladinCommand = new CreatePaladinCommand(headquarters);
  const createHouseCommand = new CreateHouseCommand(headquarters);
  const createCastleCommand = new CreateCastleCommand(headquarters);
  const gatherWoodCommand = new GatherWoodCommand(headquarters);
  const gatherFoodCommand = new GatherFoodCommand(headquarters);
  const gatherGoldCommand = new GatherGoldCommand(headquarters);
  const gatherStoneCommand = new GatherStoneCommand(headquarters);
  const showResourcesCommand = new ShowResourcesCommand(headquarters);
  const gui = new GUI();

  gui.receivePlayerCommand(createVillagerCommand);

  gui.receivePlayerCommand(createPaladinCommand);
  gui.receivePlayerCommand(gatherFoodCommand);
  gui.receivePlayerCommand(gatherFoodCommand);
  gui.receivePlayerCommand(gatherGoldCommand);
  gui.receivePlayerCommand(gatherGoldCommand);
  gui.receivePlayerCommand(createPaladinCommand);

  gui.receivePlayerCommand(createHouseCommand);
  gui.receivePlayerCommand(createHouseCommand);
  gui.receivePlayerCommand(createHouseCommand);
  gui.receivePlayerCommand(createHouseCommand);
  gui.receivePlayerCommand(createHouseCommand);
  gui.receivePlayerCommand(gatherWoodCommand);
  gui.receivePlayerCommand(createHouseCommand);
  gui.cancelPlayerCommand(createHouseCommand);
  gui.cancelPlayerCommand(createHouseCommand);

  gui.receivePlayerCommand(createCastleCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(gatherStoneCommand);
  gui.cancelPlayerCommand(gatherStoneCommand);
  gui.receivePlayerCommand(createCastleCommand);

  gui.receivePlayerCommand(showResourcesCommand);

  gui.executePlayerCommands();
}

rtsCommandSample();

總結

Command 模式是十分常見,將「行為請求者」與「行為執行者」切開來,避免兩者過度耦合,增加開發上的彈性。

但是缺點十分明顯,程式碼為了實踐分離目的,需要增加許多的程式碼,同時增加程式的複雜度。部份文章指出為了實踐設計模式會增加繁雜的設計、複雜度等等,今天總算體驗到了。

明天將介紹 Behavioural patterns 的第三個模式:Interpreter 模式。


上一篇
Day 18: Behavioral patterns - Chain of Responsibility
下一篇
Day 20: Behavioral patterns - Interpreter
系列文
也該是時候學學 Design Pattern 了31

尚未有邦友留言

立即登入留言