當系統內的物件們各自溝通的情況日益嚴重時,建立一個負責溝通的集中所,讓元件們不在「直接」,而是「間接」與他者溝通。
通常在程式剛開發的時候,為了快速達到開發目的,可能會出現物件們直接與其他物件溝通,像是:
隨著物件的數量增長,交互溝通的情況宛如人際連結的網絡般,越來越複雜,這在開發上絕對不是一件好事,日後如果有修改的需求,很容易變成上 Patch 的情況。
因此,可以建立一個負責溝通的物件,所有物件都要通過該物件才能完成溝通的工作,至少讓複雜、混亂的溝通關係簡化成星狀圖,
作法是:
以下範例以「自行車隊的行動」為核心製作。
系統內的物件親代: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);
}
系統內的物件子代:Sprinter、Domestique、TimeTrialist、ClimbingSpecialist
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("抵達終點線");
    }
}
系統內的物件親代: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; }
}
系統內的物件子代:Sprinter、Domestique、TimeTrialist、ClimbingSpecialist
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 模式。