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 模式。


<<:  Docker:KVM管理介面(virt-manager)

>>:  初浅认识Room

day2 : k8s建置(上)

基於现在kubernetes的建置实在太过容易,有kind、k3d、minikube、microk8...

Day16-Redux 篇-认识 Redux Toolkit

在这篇文章中,我们要来认识一个函式库: Redux Toolkit。 Redux Toolkit 官...

VMware Horizon Client, Compose Server, Horizon Server 每周固定时段异常无法连线 !

状况描述: esxi server 7.0.0 上面运行 vSphere Client 7.0.0 ...

[DAY 07] GridItem

接下来要说的是「单选方格」 单选方格的呈现方式是 有一个问题描述 接下来左边是n 个小题,右边是m ...

day15 : NATS 、NATS Streaming、JetStream服务应用 on K8S (上)

k8s只是一个平台,要发挥他的价值就要让适合的服务运行在上面,所以从今天开始就会介绍一些有趣的服务(...