Java設計模式百例 - 調停者模式

本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/mediator

調停者模式(Mediator Pattern)是用來降低多個對象和類之間的通信複雜性的。這種模式提供了一個調停者類,用來充當“中心化”或“總線化”的角色,與各個對象通信,從而避免了其他對象之間的互相通信,從而降低了耦合度。

例子

生活中,調停者模式的例子是相當常見的,比如:

  • 一個是講到調停者模式就避不開的關於同事之間溝通的例子。當我們身處一個大的團隊中的時候,如果工作內容涉及許多同事,那麼再互相溝通顯然成本比較高。比如張三要結婚請婚假,手中的工作要暫時交接給李四、王五等五六個同事,分別跟他們單獨溝通多麻煩,那麼直接告知組長或經理就好了,由組長或經理協調一下工作給其他同事即可;
  • 你可能會說,溝通軟件拉個羣通知一下不行嗎,當然可以,那這個時候,這個羣就相當於一個“調停者”,任何人發送的消息都彙總到羣裏,其他羣會員都可以收到消息。
  • 《Java與模式》中提到了關於WTO這種國際組織的例子,如果各個國家之間互相貿易,則互相耦合,結構複雜,如果都通過一個統一的貿易組織WTO來協調,則更加簡單高效。下邊兩個圖也是書中的,方便理解:

無調停
沒有中心化的貿易組織時,各個國家直接互相耦合,爲網狀結構。
有調停
有了中心化的貿易組織後,各個國家不直接溝通,統一與WTO耦合,爲星型結構。


通過以上例子,我們可以看出,調停者模式的作用在於:通過增加中心化的對象,將網狀的溝通結構變爲星型結構,從而降低耦合度,提高靈活性。

下面我們通過上邊提到的第一個例子來看一下調停者模式如何實現:

假設有一個團隊有兩名開發熊二、張三,有一名測試李四,有一名運維王五。他們都歸一個組長管,任何人有事情,比如請假、建議或技術分享,都需要先報告給組長,由組長統一安排。下面的故事始於張三請假:

首先是團隊組員的抽象類:

TeamMember.java

public abstract class TeamMember {
    // 團隊角色
    public static final String RD = "開發人員";
    public static final String QA = "測試人員";
    public static final String OP = "運維人員";

    // 僅與自己的組長(調停者/中心角色)維護引用關係
    private TechLeader techLeader;
    private String name;
    protected String role;

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

    public String getName() {
        return name;
    }

    public void setTechLeader(TechLeader techLeader) {
        this.techLeader = techLeader;
    }

    // 向組長(調停者/中心角色)發送信息
    public void reportToLeader(String message) {
        techLeader.memberReport(this, message);
    }

    // 收到來自組長(調停者/中心角色)的消息
    public void tempTask(String task) {
        System.out.println("[" + role + "]" + name + "收到來自組長的安排: "  + task);
    }

    // 無傷大雅的方法,與模式無關
    public abstract void dailyWork();
}

這裏有幾個與調停者模式相關的點:

  • 組員僅與組長(調停者/中心角色)維護引用關係,並不需要知道其他組員;
  • 既然是星型結構,那麼溝通方式自然是組員與組長之間的雙向溝通,組合可以發送消息給組長,也可以收到來自組長的消息(這裏通常通過提供一個回調方法實現,比如tempTask)。

那麼開發、測試和運維就是組員的具體實現了:

Developer.java

public class Developer extends TeamMember {

    public Developer(String name) {
        super(name);
        this.role = TeamMember.RD;
    }

    public void dailyWork() {
        System.out.println("我是一個碼農,我經常加班寫代碼,困了累了可能寫出bug來。");
    }

}

Tester.java

public class Tester extends TeamMember {

    public Tester(String name) {
        super(name);
        this.role = TeamMember.QA;
    }

    public void dailyWork() {
        System.out.println("我是一名測試,我找出bug,確保代碼質量。");
    }

}

Operator.java

public class Operator extends TeamMember {

    public Operator(String name) {
        super(name);
        this.role = TeamMember.OP;
    }

    public void dailyWork() {
        System.out.println("我是一個運維,保證系統穩定運行,如果有線上bug及時回滾,話說開發人員寫的程序真不穩定。");
    }

}

技術組長作爲調停者,也就是星型結構的中心角色:

TechLeader.java

public class TechLeader {
    // 維護有各個組員的引用
    private List<TeamMember> members;

    public TechLeader() {
        members = new ArrayList<TeamMember>();
    }

    public void addTeamMember(TeamMember teamMember) {
        members.add(teamMember);
        teamMember.setTechLeader(this);
    }

    public void memberReport(TeamMember reporter, String message) {
        if (message.contains("請假")) {
            reporter.tempTask("同意!");
            // 對相關人員發送消息或安排其執行操作
            for (TeamMember m : members) {
                if (m.getName().equals(reporter.getName())) {
                    continue;
                } else if (m.role.equals(TeamMember.RD)) {
                    m.tempTask(reporter.getName() + "請假了,期間請接手他的開發工作。");
                } else if (m.role.equals(TeamMember.QA)) {
                    m.tempTask(reporter.getName() + "請假了,期間請將他的bug交由其他開發人員處理。");
                } else if (m.role.equals(TeamMember.OP)) {
                    m.tempTask(reporter.getName() + "請假了,期間請將他的線上問題交由其他開發人員處理。");
                }
            }
        } else if (message.contains("建議")) {

        } else if (message.contains("技術分享")) {

        }
    }
}

關於調停者有兩個點需要注意:

  • 既然是星型結構,那麼調停者(組長)需要維護所有與之關聯的節點(組員)的引用;通常也需要提供添加節點的功能;
  • 在收到某個節點的消息後,針對其他相關節點發送消息或調用其的某些回調方法(比如例子中給其他角色安排任務)。

最後我們測試一下:

Client.java

public class Client {
    public static void main(String[] args) {
        TeamMember xionger = new Developer("熊二");
        TeamMember zhangsan = new Developer("張三");
        TeamMember lisi = new Tester("李四");
        TeamMember wangwu = new Operator("王五");

        TechLeader leader = new TechLeader();
        leader.addTeamMember(xionger);
        leader.addTeamMember(zhangsan);
        leader.addTeamMember(lisi);
        leader.addTeamMember(wangwu);

        // 張三請假
        zhangsan.reportToLeader("組長,世界很大,我想去看看,請假兩天~");
    }
}

張三向組長請假兩天,組長收到報告後安排其他組員進行應對:

[開發人員]張三收到來自組長的安排: 同意!
[開發人員]熊二收到來自組長的安排: 張三請假了,期間請接手他的開發工作。
[測試人員]李四收到來自組長的安排: 張三請假了,期間請將他的bug交由其他開發人員處理。
[運維人員]王五收到來自組長的安排: 張三請假了,期間請將他的線上問題交由其他開發人員處理。

總結

通過回顧一下上邊的例子,我們抽取出該設計模式的關鍵點:

張三請假的例子

  • 各個節點之間的網狀耦合關係變爲以調停者爲中心的星型關係,其好處是明顯的,因爲隨着節點數量的增加,網狀關係的複雜度是以階乘的速度增長的,而且網狀關係中一個節點的增刪都會設計到許多的改動。
  • 各個節點之間不在通信,全部與調停者進行雙向的通信,所以各個節點僅維護調停者的引用即可,而調停者才需要維護所有節點的引用,不過注意調停者並不一定每次都要通知全部的節點。
  • 通常每個節點都要有通知調停者的方法,以及一個用於接收調停者消息的用於被調停者回調的方法。由於具有通用性和接口性質,這兩個方法一般放在抽象類中。

調停者模式的優點

  • 鬆散耦合。調停者模式通過把多個節點對象之間的交互封裝到調停者對象裏面,從而使得節點對象之間鬆散耦合,基本上可以做到互補依賴。這樣一來,節點對象就可以獨立地變化和複用,而不再像以前那樣“牽一處而動全身”了。
  • 集中控制交互。多個節點對象的交互,被封裝在調停者對象裏面集中管理,使得這些交互行爲發生變化的時候,只需要修改調停者對象就可以了,當然如果是已經做好的系統,那麼就擴展調停者對象,而各個節點類不需要做修改。
  • 多對多變成一對多。沒有使用調停者模式的時候,節點對象之間的關係通常是多對多的,引入調停者對象以後,調停者對象和節點對象的關係通常變成雙向的一對多,這會讓對象的關係更容易理解和實現。

調停者模式的缺點

調停者模式的一個潛在缺點是,過度集中化。如果節點對象的交互非常多,而且比較複雜,當這些複雜性全部集中到調停者的時候,會導致調停者對象變得十分複雜,而且難於管理和維護。通過上邊的例子也可以初見端倪,無論是請假、建議、技術分享等等所有的處理都要交給調停者,心疼組長一分鐘~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章