Day 22: Behavioral patterns - Mediator

目的

当系统内的物件们各自沟通的情况日益严重时,建立一个负责沟通的集中所,让元件们不在「直接」,而是「间接」与他者沟通。

说明

通常在程序刚开发的时候,为了快速达到开发目的,可能会出现物件们直接与其他物件沟通,像是:

  • 物件 A 要求物件 B 的某个属性增减。
  • 物件 B 要求物件 C 执行内建方法。
  • 物件 D 要通知物件 A、B、C 某个讯息。

随着物件的数量增长,交互沟通的情况宛如人际连结的网络般,越来越复杂,这在开发上绝对不是一件好事,日後如果有修改的需求,很容易变成上 Patch 的情况。

因此,可以建立一个负责沟通的物件,所有物件都要通过该物件才能完成沟通的工作,至少让复杂、混乱的沟通关系简化成星状图,

作法是:

  1. 建立系统内的物件亲代,负责开规格,其中要建立一个关於联系 Mediator 的方法。
  2. 建立负责沟通的物件亲代,负责开规格,关於系统内所有物件的互相沟通方法都要记录下来。
  3. 建立系统内的物件子代,如果要影响他者,则在方法内跟 Mediator 沟通。除此之外,要建立其他物件会影响自身的方法。
  4. 建立负责沟通的物件子代,负责调度、实现目的。

以下范例以「自行车队的行动」为核心制作。

UML 图

Mediator Pattern UML Diagram

使用 Java 实作

系统内的物件亲代:Player

public interface Player {
    void setMediator(Mediator mediator);

    String getName();

    String getJob();
}

负责沟通的物件亲代:Mediator

public interface Mediator {
    void registerPlayer(Player player);

    void changeFormation(int type);

    void accelerate();

    void decelerate();

    void notifyTeamMembers(String message, Player requester);
}

系统内的物件子代:SprinterDomestiqueTimeTrialistClimbingSpecialist

public class Sprinter implements Player {
    private String name;
    private String job = "Sprinter";
    private Mediator mediator;

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

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

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

    @Override
    public String getJob() {
        return job;
    }

    public void teamChangeFormation(int type) {
        mediator.changeFormation(type);
    }

    public void changeFormation(int position) {
        System.out.println(job + " 收到通知,移动到车队的第 " + position + " 个位置");
    }

    public void teamAccelerate() {
        mediator.accelerate();
    }

    public void personalAccelerate() {
        System.out.println("车手 " + name + " 收到通知,开始加速");
    }

    public void teamDecelerate() {
        mediator.decelerate();
    }

    public void personalDecelerate() {
        System.out.println("车手 " + name + " 收到通知,开始减速");
    }

    public void notifyTeamMembers(String message) {
        mediator.notifyTeamMembers(message, this);
    }

    public void receiveMessage(String message) {
        System.out.println("车手 " + name + " 收到: " + message);
    }
}

public class Domestique implements Player {
    private String name;
    private String job = "Domestique";
    private Mediator mediator;

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

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

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

    @Override
    public String getJob() {
        return job;
    }

    public void teamChangeFormation(int type) {
        mediator.changeFormation(type);
    }

    public void changeFormation(int position) {
        System.out.println(job + " 收到通知,移动到车队的第 " + position + " 个位置");
    }

    public void teamAccelerate() {
        mediator.accelerate();
    }

    public void personalAccelerate() {
        System.out.println("车手 " + name + " 收到通知,开始加速");
    }

    public void teamDecelerate() {
        mediator.decelerate();
    }

    public void personalDecelerate() {
        System.out.println("车手 " + name + " 收到通知,开始减速");
    }

    public void notifyTeamMembers(String message) {
        mediator.notifyTeamMembers(message, this);
    }

    public void receiveMessage(String message) {
        System.out.println("车手 " + name + " 收到: " + message);
    }
}

public class TimeTrialist implements Player {
    private String name;
    private String job = "TimeTrialist";
    private Mediator mediator;

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

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

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

    @Override
    public String getJob() {
        return job;
    }

    public void teamChangeFormation(int type) {
        mediator.changeFormation(type);
    }

    public void changeFormation(int position) {
        System.out.println(job + " 收到通知,移动到车队的第 " + position + " 个位置");
    }

    public void teamAccelerate() {
        mediator.accelerate();
    }

    public void personalAccelerate() {
        System.out.println("车手 " + name + " 收到通知,开始加速");
    }

    public void teamDecelerate() {
        mediator.decelerate();
    }

    public void personalDecelerate() {
        System.out.println("车手 " + name + " 收到通知,开始减速");
    }

    public void notifyTeamMembers(String message) {
        mediator.notifyTeamMembers(message, this);
    }

    public void receiveMessage(String message) {
        System.out.println("车手 " + name + " 收到: " + message);
    }
}

public class ClimbingSpecialist implements Player {
    private String name;
    private String job = "ClimbingSpecialist";
    private Mediator mediator;

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

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

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

    @Override
    public String getJob() {
        return job;
    }

    public void teamChangeFormation(int type) {
        mediator.changeFormation(type);
    }

    public void changeFormation(int position) {
        System.out.println(job + " 收到通知,移动到车队的第 " + position + " 个位置");
    }

    public void teamAccelerate() {
        mediator.accelerate();
    }

    public void personalAccelerate() {
        System.out.println("车手 " + name + " 收到通知,开始加速");
    }

    public void teamDecelerate() {
        mediator.decelerate();
    }

    public void personalDecelerate() {
        System.out.println("车手 " + name + " 收到通知,开始减速");
    }

    public void notifyTeamMembers(String message) {
        mediator.notifyTeamMembers(message, this);
    }

    public void receiveMessage(String message) {
        System.out.println("车手 " + name + " 收到: " + message);
    }
}

负责沟通的物件子代:PitStop(Mediator 物件)

public class PitStop implements Mediator {
    private Sprinter sprinter;
    private Domestique domestique;
    private TimeTrialist timeTrialist;
    private ClimbingSpecialist climbingSpecialist;

    @Override
    public void registerPlayer(Player player) {
        player.setMediator(this);

        switch (player.getJob()) {
            case "Sprinter":
                sprinter = (Sprinter) player;
                break;
            case "Domestique":
                domestique = (Domestique) player;
                break;
            case "TimeTrialist":
                timeTrialist = (TimeTrialist) player;
                break;
            case "ClimbingSpecialist":
                climbingSpecialist = (ClimbingSpecialist) player;
                break;
        }

    }

    @Override
    public void changeFormation(int type) {
        switch (type) {
            case 1:
                sprinter.changeFormation(1);
                domestique.changeFormation(2);
                timeTrialist.changeFormation(3);
                climbingSpecialist.changeFormation(4);
                break;
            case 2:
                domestique.changeFormation(1);
                timeTrialist.changeFormation(2);
                climbingSpecialist.changeFormation(3);
                sprinter.changeFormation(4);
                break;
            case 3:
                climbingSpecialist.changeFormation(1);
                domestique.changeFormation(2);
                sprinter.changeFormation(3);
                timeTrialist.changeFormation(4);
                break;
            case 4:
                timeTrialist.changeFormation(1);
                climbingSpecialist.changeFormation(2);
                sprinter.changeFormation(3);
                domestique.changeFormation(4);
                break;
        }

        System.out.println("");
    }

    @Override
    public void accelerate() {
        sprinter.personalAccelerate();
        domestique.personalAccelerate();
        timeTrialist.personalAccelerate();
        climbingSpecialist.personalAccelerate();
        System.out.println("");
    }

    @Override
    public void decelerate() {
        sprinter.personalDecelerate();
        domestique.personalDecelerate();
        timeTrialist.personalDecelerate();
        climbingSpecialist.personalDecelerate();
        System.out.println("");
    }

    @Override
    public void notifyTeamMembers(String message, Player requester) {
        String finalMessage = "车手: " + requester.getName() + " 通知: " + message;

        switch (requester.getJob()) {
            case "Sprinter":
                domestique.receiveMessage(finalMessage);
                timeTrialist.receiveMessage(finalMessage);
                climbingSpecialist.receiveMessage(finalMessage);
                break;
            case "Domestique":
                sprinter.receiveMessage(finalMessage);
                timeTrialist.receiveMessage(finalMessage);
                climbingSpecialist.receiveMessage(finalMessage);
                break;
            case "TimeTrialist":
                sprinter.receiveMessage(finalMessage);
                domestique.receiveMessage(finalMessage);
                climbingSpecialist.receiveMessage(finalMessage);
                break;
            case "ClimbingSpecialist":
                sprinter.receiveMessage(finalMessage);
                domestique.receiveMessage(finalMessage);
                timeTrialist.receiveMessage(finalMessage);
                break;
        }

        System.out.println("");
    }

}

测试,建立车队成员後,模拟沟通情境:BicycleRacingMediatorSample

public class BicycleRacingMediatorSample {

    public static void main(String[] args) {
        Mediator mediator = new PitStop();
        Sprinter sprinter = new Sprinter("Roger");
        Domestique domestique = new Domestique("Victor");
        TimeTrialist timeTrialist = new TimeTrialist("David");
        ClimbingSpecialist climbingSpecialist = new ClimbingSpecialist("Eric");

        /** 注册队友 */
        mediator.registerPlayer(sprinter);
        mediator.registerPlayer(domestique);
        mediator.registerPlayer(timeTrialist);
        mediator.registerPlayer(climbingSpecialist);

        /** 要求加速 */
        sprinter.teamAccelerate();
        domestique.teamAccelerate();

        /** 要求减速 */
        timeTrialist.teamDecelerate();
        climbingSpecialist.teamChangeFormation(2);

        /** 通知队友 */
        domestique.notifyTeamMembers("前方有大弯道,向右");

        /** 最後冲刺 */
        sprinter.teamChangeFormation(1);
        sprinter.notifyTeamMembers("抵达终点线");
    }

}

使用 JavaScript 实作

系统内的物件亲代:Player

/** @interface */
class Player {
  /** @param {Mediator} mediator */
  setMediator(mediator) { return };

  getName() { return ""; }

  getJob() { return ""; }
}

负责沟通的物件亲代:Mediator

/** @interface */
class Mediator {
  /** @param {Player} player */
  registerPlayer(player) { return; }

  /** @param {number} type */
  changeFormation(type) { return; }

  accelerate() { return; }

  decelerate() { return; }

  /**
   * @param {string} message
   * @param {Player} requester
   */
  notifyTeamMembers(message, requester) { return; }
}

系统内的物件子代:SprinterDomestiqueTimeTrialistClimbingSpecialist

class Sprinter extends Player {
  constructor(name) {
    super();
    /** @type {string} */
    this.name = name;
    this.job = "Sprinter";
    /** @type {Mediator} */
    this.mediator = null;
  }

  /**
   * @override
   * @param {Mediator} mediator
  */
  setMediator(mediator) {
    this.mediator = mediator;
  }

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

  /** @override */
  getJob() {
    return this.job;
  }

  /** @param {number} type */
  teamChangeFormation(type) {
    this.mediator.changeFormation(type);
  }

  /** @param {number} position */
  changeFormation(position) {
    console.log(this.job + " 收到通知,移动到车队的第 " + position + " 个位置");
  }

  teamAccelerate() {
    this.mediator.accelerate();
  }

  personalAccelerate() {
    console.log("车手 " + this.name + " 收到通知,开始加速");
  }

  teamDecelerate() {
    this.mediator.decelerate();
  }

  personalDecelerate() {
    console.log("车手 " + this.name + " 收到通知,开始减速");
  }

  /** @param {string} message */
  notifyTeamMembers(message) {
    this.mediator.notifyTeamMembers(message, this);
  }

  /** @param {string} message */
  receiveMessage(message) {
    console.log("车手 " + this.name + " 收到: " + message);
  }
}

class Domestique extends Player {

  constructor(name) {
    super();
    /** @type {string} */
    this.name = name;
    this.job = "Domestique";
    /** @type {Mediator} */
    this.mediator = null;
  }

  /**
   * @override
   * @param {Mediator} mediator
  */
  setMediator(mediator) {
    this.mediator = mediator;
  }

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

  /** @override */
  getJob() {
    return this.job;
  }

  /** @param {number} type */
  teamChangeFormation(type) {
    this.mediator.changeFormation(type);
  }

  /** @param {number} position */
  changeFormation(position) {
    console.log(this.job + " 收到通知,移动到车队的第 " + position + " 个位置");
  }

  teamAccelerate() {
    this.mediator.accelerate();
  }

  personalAccelerate() {
    console.log("车手 " + this.name + " 收到通知,开始加速");
  }

  teamDecelerate() {
    this.mediator.decelerate();
  }

  personalDecelerate() {
    console.log("车手 " + this.name + " 收到通知,开始减速");
  }

  /** @param {string} message */
  notifyTeamMembers(message) {
    this.mediator.notifyTeamMembers(message, this);
  }

  /** @param {string} message */
  receiveMessage(message) {
    console.log("车手 " + this.name + " 收到: " + message);
  }
}

class TimeTrialist extends Player {
  constructor(name) {
    super();
    /** @type {string} */
    this.name = name;
    this.job = "TimeTrialist";
    /** @type {Mediator} */
    this.mediator = null;
  }

  /**
   * @override
   * @param {Mediator} mediator
  */
  setMediator(mediator) {
    this.mediator = mediator;
  }

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

  /** @override */
  getJob() {
    return this.job;
  }

  /** @param {number} type */
  teamChangeFormation(type) {
    this.mediator.changeFormation(type);
  }

  /** @param {number} position */
  changeFormation(position) {
    console.log(this.job + " 收到通知,移动到车队的第 " + position + " 个位置");
  }

  teamAccelerate() {
    this.mediator.accelerate();
  }

  personalAccelerate() {
    console.log("车手 " + this.name + " 收到通知,开始加速");
  }

  teamDecelerate() {
    this.mediator.decelerate();
  }

  personalDecelerate() {
    console.log("车手 " + this.name + " 收到通知,开始减速");
  }

  /** @param {string} message */
  notifyTeamMembers(message) {
    this.mediator.notifyTeamMembers(message, this);
  }

  /** @param {string} message */
  receiveMessage(message) {
    console.log("车手 " + this.name + " 收到: " + message);
  }
}

class ClimbingSpecialist extends Player {
  constructor(name) {
    super();
    /** @type {string} */
    this.name = name;
    this.job = "ClimbingSpecialist";
    /** @type {Mediator} */
    this.mediator = null;
  }

  /**
   * @override
   * @param {Mediator} mediator
  */
  setMediator(mediator) {
    this.mediator = mediator;
  }

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

  /** @override */
  getJob() {
    return this.job;
  }

  /** @param {number} type */
  teamChangeFormation(type) {
    this.mediator.changeFormation(type);
  }

  /** @param {number} position */
  changeFormation(position) {
    console.log(this.job + " 收到通知,移动到车队的第 " + position + " 个位置");
  }

  teamAccelerate() {
    this.mediator.accelerate();
  }

  personalAccelerate() {
    console.log("车手 " + this.name + " 收到通知,开始加速");
  }

  teamDecelerate() {
    this.mediator.decelerate();
  }

  personalDecelerate() {
    console.log("车手 " + this.name + " 收到通知,开始减速");
  }

  /** @param {string} message */
  notifyTeamMembers(message) {
    this.mediator.notifyTeamMembers(message, this);
  }

  /** @param {string} message */
  receiveMessage(message) {
    console.log("车手 " + this.name + " 收到: " + message);
  }
}

负责沟通的物件子代:PitStop(Mediator 物件)

class PitStop extends Mediator {
  constructor() {
    super();
    /** @type {Sprinter} */
    this.sprinter = null;
    /** @type {Domestique} */
    this.domestique = null;
    /** @type {TimeTrialist} */
    this.timeTrialist = null;
    /** @type {ClimbingSpecialist} */
    this.climbingSpecialist = null;
  }

  /**
   * @override
   * @param {Player} player
   */
  registerPlayer(player) {
    player.setMediator(this);

    switch (player.getJob()) {
      case "Sprinter":
        this.sprinter = player;
        break;
      case "Domestique":
        this.domestique = player;
        break;
      case "TimeTrialist":
        this.timeTrialist = player;
        break;
      case "ClimbingSpecialist":
        this.climbingSpecialist = player;
        break;
    }

  }

  /**
   * @override
   * @param {number} type
   */
  changeFormation(type) {
    switch (type) {
      case 1:
        this.sprinter.changeFormation(1);
        this.domestique.changeFormation(2);
        this.timeTrialist.changeFormation(3);
        this.climbingSpecialist.changeFormation(4);
        break;
      case 2:
        this.domestique.changeFormation(1);
        this.timeTrialist.changeFormation(2);
        this.climbingSpecialist.changeFormation(3);
        this.sprinter.changeFormation(4);
        break;
      case 3:
        this.climbingSpecialist.changeFormation(1);
        this.domestique.changeFormation(2);
        this.sprinter.changeFormation(3);
        this.timeTrialist.changeFormation(4);
        break;
      case 4:
        this.timeTrialist.changeFormation(1);
        this.climbingSpecialist.changeFormation(2);
        this.sprinter.changeFormation(3);
        this.domestique.changeFormation(4);
        break;
    }

    console.log("");
  }

  /** @override */
  accelerate() {
    this.sprinter.personalAccelerate();
    this.domestique.personalAccelerate();
    this.timeTrialist.personalAccelerate();
    this.climbingSpecialist.personalAccelerate();
    console.log("");
  }

  /** @override */
  decelerate() {
    this.sprinter.personalDecelerate();
    this.domestique.personalDecelerate();
    this.timeTrialist.personalDecelerate();
    this.climbingSpecialist.personalDecelerate();
    console.log("");
  }

  /**
   * @override
   * @param {string} message
   * @param {Player} requester
   */
  notifyTeamMembers(message, requester) {
    const finalMessage = "车手: " + requester.getName() + " 通知: " + message;

    switch (requester.getJob()) {
      case "Sprinter":
        this.domestique.receiveMessage(finalMessage);
        this.timeTrialist.receiveMessage(finalMessage);
        this.climbingSpecialist.receiveMessage(finalMessage);
        break;
      case "Domestique":
        this.sprinter.receiveMessage(finalMessage);
        this.timeTrialist.receiveMessage(finalMessage);
        this.climbingSpecialist.receiveMessage(finalMessage);
        break;
      case "TimeTrialist":
        this.sprinter.receiveMessage(finalMessage);
        this.domestique.receiveMessage(finalMessage);
        this.climbingSpecialist.receiveMessage(finalMessage);
        break;
      case "ClimbingSpecialist":
        this.sprinter.receiveMessage(finalMessage);
        this.domestique.receiveMessage(finalMessage);
        this.timeTrialist.receiveMessage(finalMessage);
        break;
    }

    console.log("");
  }
}

测试,建立车队成员後,模拟沟通情境:BicycleRacingMediatorSample

const bicycleRacingMediatorSample = () => {
  const mediator = new PitStop();
  const sprinter = new Sprinter("Roger");
  const domestique = new Domestique("Victor");
  const timeTrialist = new TimeTrialist("David");
  const climbingSpecialist = new ClimbingSpecialist("Eric");

  /** 注册队友 */
  mediator.registerPlayer(sprinter);
  mediator.registerPlayer(domestique);
  mediator.registerPlayer(timeTrialist);
  mediator.registerPlayer(climbingSpecialist);

  /** 要求加速 */
  sprinter.teamAccelerate();
  domestique.teamAccelerate();

  /** 要求减速 */
  timeTrialist.teamDecelerate();
  climbingSpecialist.teamChangeFormation(2);

  /** 通知队友 */
  domestique.notifyTeamMembers("前方有大弯道,向右");

  /** 最後冲刺 */
  sprinter.teamChangeFormation(1);
  sprinter.notifyTeamMembers("抵达终点线");
};

bicycleRacingMediatorSample();

总结

起初,我以为 Mediator 模式跟 React 或是 Redux 的中心式管理 Data 相似,实则完全不同。因为 Mediator 模式的中间人不负责储存变数,只负责「传递」,物件之间互相影响後的状态如何,中间人都不会知道。

该模式玩味之处还有一点,让所有物件都依赖中间人,造成该中间人成为超级重要的 God Class,变相增加物件的依赖性,这会在往後成为开发上的障碍。

因此,此模式适合在确定物件之间有着高度依赖性,并且往後的变动不多、不大,那就可以安心使用了。

明天将介绍 Behavioural patterns 的第六个模式:Memento 模式。


<<:  [第二十二只羊] 迷雾森林舞会XVI 策略模式 Strategy Pattern

>>:  Day22 Redis架构实战-高可用性使用Sentinel机制

Angular 深入浅出三十天:表单与测试 Day20 - E2E 测试实作 - 登入系统

经过这两天的介绍,相信大家对於 Cypress 应该已经有了一定程度的理解,有没有人已经开始用它来...

【PHP Telegram Bot】Day05 - 程序语言的运作原理

众所皆知:「不会写程序的人都认为程序语言是写给电脑看的, 会写程序的人都知道程序语言是写给人看的。...

Day 27 -资料库应用小程序 首页功能(内涵程序码)

废话不多说直接开始 在开启一个专案放以下两个cs DBconnection.cs: using My...

DAY5:专案架构介绍(一)

接下来我要来介绍到有关於当我们将专案打开来时,那最多人使用的配置是”Project”及”androi...

Day.14 「基础打稳了,就能走得更长久~」 —— JavaScript 基础运算子

学习任何东西,都要把基础学的扎实,基础稳了,遇到问题就能迎刃而解。 而学习程序语言的基础就是数学逻...