iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Software Development

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

Day 18: Behavioral patterns - Chain of Responsibility

目的

當一個行為需要經過多道步驟時,可以將步驟串在一起。

說明

該模式常見於兩種情境:

  1. 行為不一定一個步驟就執行完畢,可能第一個步驟的「權限」不夠大,需要依賴下個步驟,如果下個步驟權限也不夠就繼續往下,直到最後一個最大權限的負責執行
  2. 行為一定要完成所有的步驟,任何一個步驟未通過就失敗、拒絕。

以上兩個情境轉換到現實則是:

  1. 職員的請假、加薪請求,小主管權限不夠向上傳遞給中主管、大主管、老闆等等。
  2. 權限的驗證,必須通過所有驗證才算通過。

實踐的作法是:

  1. 建立虛擬層親代開規格,關於每個步驟的判斷,以及下個步驟的連結。
  2. 建立步驟子代,每個步驟都繼承虛擬層。
  3. 實作每個步驟,依照情境不同在實作上:
    1. 情境一,如果本身的權限不足,則跳到下個步驟,直到權限足夠的完成判定。
    2. 情境二,如果該步驟的條件未完成,則判定失格。
  4. 設定每個步驟的下一步。
  5. 執行。

將相關步驟串在一起的好處是,步驟變更時,只要更換步驟的下一步就好,不用動到太多程式碼。

以下範例以需求「權限的驗證,必須通過所有驗證才算通過」為核心製作。

UML 圖

Chain of Responsibility Pattern UML Diagram

使用 Java 實作

徽章物件、使用者物件:BadgeUser

public class Badge {
    private String name;

    public Badge(String name) {
        this.name = name;
    }

    public String show() {
        return name;
    }
}

public class Player {
    private String name;
    private HashMap<String, Badge> badgeBox = new HashMap<>();

    public Player(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void addNewBadge(Badge badge) {
        badgeBox.put(badge.show(), badge);
    }

    public Badge showBadge(String badgeName) {
        return badgeBox.get(badgeName);
    }
}

步驟的虛擬層親代:Doorkeeper

public abstract class Doorkeeper {
    protected String name;

    protected Doorkeeper nextDoorkeeper;

    protected Doorkeeper(String name) {
        this.name = name;
    }

    public void setNextDoorkeeper(Doorkeeper doorkeeper) {
        this.nextDoorkeeper = doorkeeper;
    }

    public abstract boolean check(Player player);
}

步驟子代:BoulderBadgeDoorkeeperCascadeBadgeDoorkeeperThunderBadgeDoorkeeperRainbowBadgeDoorkeeperSoulBadgeDoorkeeperMarshBadgeDoorkeeperVolcanoBadgeDoorkeeperEarthBadgeDoorkeeper(Chain of Responsibility 物件)

public class BoulderBadgeDoorkeeper extends Doorkeeper {
    public BoulderBadgeDoorkeeper() {
        super("灰色徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有灰色徽章");
        Badge badge = player.showBadge("Boulder");

        if (badge == null) {
            System.out.println("你未持有灰色徽章,不能通過此門\n");
            return false;
        }

        System.out.println("你持有灰色徽章,請通過此門\n");
        return nextDoorkeeper.check(player);
    }
}

public class CascadeBadgeDoorkeeper extends Doorkeeper {
    public CascadeBadgeDoorkeeper() {
        super("藍色徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有藍色徽章");
        Badge badge = player.showBadge("Cascade");

        if (badge == null) {
            System.out.println("你未持有藍色徽章,不能通過此門\n");
            return false;
        }

        System.out.println("你持有藍色徽章,請通過此門\n");
        return nextDoorkeeper.check(player);
    }
}

public class ThunderBadgeDoorkeeper extends Doorkeeper {
    public ThunderBadgeDoorkeeper() {
        super("橙色徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有橙色徽章");
        Badge badge = player.showBadge("Thunder");

        if (badge == null) {
            System.out.println("你未持有橙色徽章,不能通過此門\n");
            return false;
        }

        System.out.println("你持有橙色徽章,請通過此門\n");
        return nextDoorkeeper.check(player);
    }
}

public class RainbowBadgeDoorkeeper extends Doorkeeper {
    public RainbowBadgeDoorkeeper() {
        super("彩虹徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有彩虹徽章");
        Badge badge = player.showBadge("Rainbow");

        if (badge == null) {
            System.out.println("你未持有彩虹徽章,不能通過此門\n");
            return false;
        }

        System.out.println("你持有彩虹徽章,請通過此門\n");
        return nextDoorkeeper.check(player);
    }
}

public class SoulBadgeDoorkeeper extends Doorkeeper {
    public SoulBadgeDoorkeeper() {
        super("粉紅徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有粉紅徽章");
        Badge badge = player.showBadge("Soul");

        if (badge == null) {
            System.out.println("你未持有粉紅徽章,不能通過此門\n");
            return false;
        }

        System.out.println("你持有粉紅徽章,請通過此門\n");
        return nextDoorkeeper.check(player);
    }
}

public class MarshBadgeDoorkeeper extends Doorkeeper {
    public MarshBadgeDoorkeeper() {
        super("金色徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有金色徽章");
        Badge badge = player.showBadge("Marsh");

        if (badge == null) {
            System.out.println("你未持有金色徽章,不能通過此門\n");
            return false;
        }

        System.out.println("你持有金色徽章,請通過此門\n");
        return nextDoorkeeper.check(player);
    }
}

public class VolcanoBadgeDoorkeeper extends Doorkeeper {
    public VolcanoBadgeDoorkeeper() {
        super("深紅徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有深紅徽章");
        Badge badge = player.showBadge("Volcano");

        if (badge == null) {
            System.out.println("你未持有深紅徽章,不能通過此門\n");
            return false;
        }

        System.out.println("你持有深紅徽章,請通過此門\n");
        return nextDoorkeeper.check(player);
    }
}

public class EarthBadgeDoorkeeper extends Doorkeeper {
    public EarthBadgeDoorkeeper() {
        super("綠色徽章檢查者");
    }

    @Override
    public boolean check(Player player) {
        System.out.println("我是 " + name + ",負責檢查是否有綠色徽章");
        Badge badge = player.showBadge("Earth");

        if (badge == null) {
            System.out.println("你未持有綠色徽章,不能通過此門");
            return false;
        }

        System.out.println("你持有綠色徽章,請通過此門\n");
        return true;
    }
}

測試,挑戰者小智嘗試通過冠軍之路的徽章檢查:VictoryRoadChainOfResponsibilitySample

public class VictoryRoadChainOfResponsibilitySample {
    public static void main(String[] args) {
        Player player = new Player("小智");
        player.addNewBadge(new Badge("Boulder"));
        player.addNewBadge(new Badge("Cascade"));
        player.addNewBadge(new Badge("Thunder"));
        player.addNewBadge(new Badge("Rainbow"));
        player.addNewBadge(new Badge("Soul"));
        player.addNewBadge(new Badge("Marsh"));
        player.addNewBadge(new Badge("Volcano"));
        player.addNewBadge(new Badge("Earth"));

        Doorkeeper boulderBadgeDoorkeeper = new BoulderBadgeDoorkeeper();
        Doorkeeper cascadeBadgeDoorkeeper = new CascadeBadgeDoorkeeper();
        Doorkeeper thunderBadgeDoorkeeper = new ThunderBadgeDoorkeeper();
        Doorkeeper rainbowBadgeDoorkeeper = new RainbowBadgeDoorkeeper();
        Doorkeeper soulBadgeDoorkeeper = new SoulBadgeDoorkeeper();
        Doorkeeper marshBadgeDoorkeeper = new MarshBadgeDoorkeeper();
        Doorkeeper volcanoBadgeDoorkeeper = new VolcanoBadgeDoorkeeper();
        Doorkeeper earthBadgeDoorkeeper = new EarthBadgeDoorkeeper();

        boulderBadgeDoorkeeper.setNextDoorkeeper(cascadeBadgeDoorkeeper);
        cascadeBadgeDoorkeeper.setNextDoorkeeper(thunderBadgeDoorkeeper);
        thunderBadgeDoorkeeper.setNextDoorkeeper(rainbowBadgeDoorkeeper);
        rainbowBadgeDoorkeeper.setNextDoorkeeper(soulBadgeDoorkeeper);
        soulBadgeDoorkeeper.setNextDoorkeeper(marshBadgeDoorkeeper);
        marshBadgeDoorkeeper.setNextDoorkeeper(volcanoBadgeDoorkeeper);
        volcanoBadgeDoorkeeper.setNextDoorkeeper(earthBadgeDoorkeeper);

        boolean check = boulderBadgeDoorkeeper.check(player);

        if (check) {
            System.out.println("挑戰者 " + player.getName() + " 通過冠軍之路");
        } else {
            System.out.println("請繼續挑戰各地的道館,直到獲得八個徽章吧");
        }
    }
}

使用 JavaScript 實作

徽章物件、使用者物件:BadgeUser

class Badge {
  /** @param {string} name */
  constructor(name) {
    this.name = name;
  }

  show() {
    return this.name;
  }
}

class Player {
  /** @param {string} name */
  constructor(name) {
    this.name = name;
    /** @type {Map<string, Badge>} */
    this.badgeBox = new Map();
  }

  getName() {
    return this.name;
  }

  /** @param {Badge} badge */
  addNewBadge(badge) {
    this.badgeBox.set(badge.show(), badge);
  }

  /** @param {string} badgeName */
  showBadge(badgeName) {
    if (this.badgeBox.has(badgeName)) {
      return this.badgeBox.get(badgeName);
    } else {
      return null;
    }
  }
}

步驟的虛擬層親代:Doorkeeper

/** @abstract */
class Doorkeeper {
  /** @param {string} name */
  constructor(name) {
    this.name = name;
    /** @type {Doorkeeper} */
    this.nextDoorkeeper = null;
  }

  /** @param {Doorkeeper} doorkeeper */
  setNextDoorkeeper(doorkeeper) {
    this.nextDoorkeeper = doorkeeper;
  }

  /**
   * @abstract
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) { return; }
}

步驟子代:BoulderBadgeDoorkeeperCascadeBadgeDoorkeeperThunderBadgeDoorkeeperRainbowBadgeDoorkeeperSoulBadgeDoorkeeperMarshBadgeDoorkeeperVolcanoBadgeDoorkeeperEarthBadgeDoorkeeper(Chain of Responsibility 物件)

class BoulderBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("灰色徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有灰色徽章");
    const badge = player.showBadge("Boulder");

    if (badge == null) {
      console.log("你未持有灰色徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有灰色徽章,請通過此門\n");
    return this.nextDoorkeeper.check(player);
  }
}

class CascadeBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("藍色徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有藍色徽章");
    const badge = player.showBadge("Cascade");

    if (badge == null) {
      console.log("你未持有藍色徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有藍色徽章,請通過此門\n");
    return this.nextDoorkeeper.check(player);
  }
}

class ThunderBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("橙色徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有橙色徽章");
    const badge = player.showBadge("Thunder");

    if (badge == null) {
      console.log("你未持有橙色徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有橙色徽章,請通過此門\n");
    return this.nextDoorkeeper.check(player);
  }
}

class RainbowBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("彩虹徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有彩虹徽章");
    const badge = player.showBadge("Rainbow");

    if (badge == null) {
      console.log("你未持有彩虹徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有彩虹徽章,請通過此門\n");
    return this.nextDoorkeeper.check(player);
  }
}

class SoulBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("粉紅徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有粉紅徽章");
    const badge = player.showBadge("Soul");

    if (badge == null) {
      console.log("你未持有粉紅徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有粉紅徽章,請通過此門\n");
    return this.nextDoorkeeper.check(player);
  }
}

class MarshBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("金色徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有金色徽章");
    const badge = player.showBadge("Marsh");

    if (badge == null) {
      console.log("你未持有金色徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有金色徽章,請通過此門\n");
    return this.nextDoorkeeper.check(player);
  }
}


class VolcanoBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("深紅徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有深紅徽章");
    const badge = player.showBadge("Volcano");

    if (badge == null) {
      console.log("你未持有深紅徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有深紅徽章,請通過此門\n");
    return this.nextDoorkeeper.check(player);
  }
}

class EarthBadgeDoorkeeper extends Doorkeeper {
  constructor() {
    super("綠色徽章檢查者");
  }

  /**
   * @override
   * @param {Player} player
   * @returns {boolean}
   */
  check(player) {
    console.log("我是 " + this.name + ",負責檢查是否有綠色徽章");
    const badge = player.showBadge("Earth");

    if (badge == null) {
      console.log("你未持有綠色徽章,不能通過此門\n");
      return false;
    }

    console.log("你持有綠色徽章,請通過此門\n");
    return true;
  }
}

測試,挑戰者小智嘗試通過冠軍之路的徽章檢查:victoryRoadChainOfResponsibilitySample

const victoryRoadChainOfResponsibilitySample = () => {
  const player = new Player("小智");
  player.addNewBadge(new Badge("Boulder"));
  player.addNewBadge(new Badge("Cascade"));
  player.addNewBadge(new Badge("Thunder"));
  player.addNewBadge(new Badge("Rainbow"));
  player.addNewBadge(new Badge("Soul"));
  player.addNewBadge(new Badge("Marsh"));
  player.addNewBadge(new Badge("Volcano"));
  player.addNewBadge(new Badge("Earth"));

  const boulderBadgeDoorkeeper = new BoulderBadgeDoorkeeper();
  const cascadeBadgeDoorkeeper = new CascadeBadgeDoorkeeper();
  const thunderBadgeDoorkeeper = new ThunderBadgeDoorkeeper();
  const rainbowBadgeDoorkeeper = new RainbowBadgeDoorkeeper();
  const soulBadgeDoorkeeper = new SoulBadgeDoorkeeper();
  const marshBadgeDoorkeeper = new MarshBadgeDoorkeeper();
  const volcanoBadgeDoorkeeper = new VolcanoBadgeDoorkeeper();
  const earthBadgeDoorkeeper = new EarthBadgeDoorkeeper();

  boulderBadgeDoorkeeper.setNextDoorkeeper(cascadeBadgeDoorkeeper);
  cascadeBadgeDoorkeeper.setNextDoorkeeper(thunderBadgeDoorkeeper);
  thunderBadgeDoorkeeper.setNextDoorkeeper(rainbowBadgeDoorkeeper);
  rainbowBadgeDoorkeeper.setNextDoorkeeper(soulBadgeDoorkeeper);
  soulBadgeDoorkeeper.setNextDoorkeeper(marshBadgeDoorkeeper);
  marshBadgeDoorkeeper.setNextDoorkeeper(volcanoBadgeDoorkeeper);
  volcanoBadgeDoorkeeper.setNextDoorkeeper(earthBadgeDoorkeeper);

  const check = boulderBadgeDoorkeeper.check(player);

  if (check) {
    console.log("挑戰者 " + player.getName() + " 通過冠軍之路");
  } else {
    console.log("請繼續挑戰各地的道館,直到獲得八個徽章吧");
  }
}

victoryRoadChainOfResponsibilitySample();

總結

光看名稱,很難想像 Chain of Responsibility 模式的功用,當理解需求是轉換需要多步驟的行為時,頓時豁然開朗。同時,理解該模式是針對特殊情況下的解,難以用於其他情境下。

明天將介紹 Behavioural patterns 的第二個模式:Command 模式。


上一篇
Day 17: Structural patterns - Proxy
下一篇
Day 19: Behavioral patterns - Command
系列文
也該是時候學學 Design Pattern 了31

尚未有邦友留言

立即登入留言