本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/mediator
調(diào)停者模式(Mediator Pattern)是用來降低多個對象和類之間的通信復(fù)雜性的斧账。這種模式提供了一個調(diào)停者類啃沪,用來充當(dāng)“中心化”或“總線化”的角色蹄溉,與各個對象通信吃靠,從而避免了其他對象之間的互相通信,從而降低了耦合度烙样。
例子
生活中开仰,調(diào)停者模式的例子是相當(dāng)常見的,比如:
- 一個是講到調(diào)停者模式就避不開的關(guān)于同事之間溝通的例子颂郎。當(dāng)我們身處一個大的團隊中的時候吼渡,如果工作內(nèi)容涉及許多同事,那么再互相溝通顯然成本比較高乓序。比如張三要結(jié)婚請婚假寺酪,手中的工作要暫時交接給李四、王五等五六個同事替劈,分別跟他們單獨溝通多麻煩寄雀,那么直接告知組長或經(jīng)理就好了,由組長或經(jīng)理協(xié)調(diào)一下工作給其他同事即可陨献;
- 你可能會說盒犹,溝通軟件拉個群通知一下不行嗎,當(dāng)然可以,那這個時候阿趁,這個群就相當(dāng)于一個“調(diào)停者”膜蛔,任何人發(fā)送的消息都匯總到群里,其他群會員都可以收到消息脖阵。
- 《Java與模式》中提到了關(guān)于WTO這種國際組織的例子皂股,如果各個國家之間互相貿(mào)易,則互相耦合命黔,結(jié)構(gòu)復(fù)雜呜呐,如果都通過一個統(tǒng)一的貿(mào)易組織WTO來協(xié)調(diào),則更加簡單高效悍募。下邊兩個圖也是書中的蘑辑,方便理解:
沒有中心化的貿(mào)易組織時,各個國家直接互相耦合坠宴,為網(wǎng)狀結(jié)構(gòu)洋魂。
有了中心化的貿(mào)易組織后,各個國家不直接溝通喜鼓,統(tǒng)一與WTO耦合副砍,為星型結(jié)構(gòu)。
通過以上例子庄岖,我們可以看出豁翎,調(diào)停者模式的作用在于:通過增加中心化的對象,將網(wǎng)狀的溝通結(jié)構(gòu)變?yōu)樾切徒Y(jié)構(gòu)隅忿,從而降低耦合度心剥,提高靈活性。
下面我們通過上邊提到的第一個例子來看一下調(diào)停者模式如何實現(xiàn):
假設(shè)有一個團隊有兩名開發(fā)熊二背桐、張三优烧,有一名測試?yán)钏模幸幻\維王五链峭。他們都歸一個組長管畦娄,任何人有事情,比如請假熏版、建議或技術(shù)分享纷责,都需要先報告給組長捍掺,由組長統(tǒng)一安排撼短。下面的故事始于張三請假:
首先是團隊組員的抽象類:
TeamMember.java
public abstract class TeamMember {
// 團隊角色
public static final String RD = "開發(fā)人員";
public static final String QA = "測試人員";
public static final String OP = "運維人員";
// 僅與自己的組長(調(diào)停者/中心角色)維護引用關(guān)系
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;
}
// 向組長(調(diào)停者/中心角色)發(fā)送信息
public void reportToLeader(String message) {
techLeader.memberReport(this, message);
}
// 收到來自組長(調(diào)停者/中心角色)的消息
public void tempTask(String task) {
System.out.println("[" + role + "]" + name + "收到來自組長的安排: " + task);
}
// 無傷大雅的方法,與模式無關(guān)
public abstract void dailyWork();
}
這里有幾個與調(diào)停者模式相關(guān)的點:
- 組員僅與組長(調(diào)停者/中心角色)維護引用關(guān)系挺勿,并不需要知道其他組員曲横;
- 既然是星型結(jié)構(gòu),那么溝通方式自然是組員與組長之間的雙向溝通,組合可以發(fā)送消息給組長禾嫉,也可以收到來自組長的消息(這里通常通過提供一個回調(diào)方法實現(xiàn)灾杰,比如
tempTask
)。
那么開發(fā)熙参、測試和運維就是組員的具體實現(xiàn)了:
Developer.java
public class Developer extends TeamMember {
public Developer(String name) {
super(name);
this.role = TeamMember.RD;
}
public void dailyWork() {
System.out.println("我是一個碼農(nóng)艳吠,我經(jīng)常加班寫代碼,困了累了可能寫出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,確保代碼質(zhì)量黍匾。");
}
}
Operator.java
public class Operator extends TeamMember {
public Operator(String name) {
super(name);
this.role = TeamMember.OP;
}
public void dailyWork() {
System.out.println("我是一個運維栏渺,保證系統(tǒng)穩(wěn)定運行,如果有線上bug及時回滾锐涯,話說開發(fā)人員寫的程序真不穩(wěn)定磕诊。");
}
}
技術(shù)組長作為調(diào)停者,也就是星型結(jié)構(gòu)的中心角色:
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("同意纹腌!");
// 對相關(guān)人員發(fā)送消息或安排其執(zhí)行操作
for (TeamMember m : members) {
if (m.getName().equals(reporter.getName())) {
continue;
} else if (m.role.equals(TeamMember.RD)) {
m.tempTask(reporter.getName() + "請假了霎终,期間請接手他的開發(fā)工作。");
} else if (m.role.equals(TeamMember.QA)) {
m.tempTask(reporter.getName() + "請假了壶笼,期間請將他的bug交由其他開發(fā)人員處理神僵。");
} else if (m.role.equals(TeamMember.OP)) {
m.tempTask(reporter.getName() + "請假了,期間請將他的線上問題交由其他開發(fā)人員處理覆劈。");
}
}
} else if (message.contains("建議")) {
} else if (message.contains("技術(shù)分享")) {
}
}
}
關(guān)于調(diào)停者有兩個點需要注意:
- 既然是星型結(jié)構(gòu)保礼,那么調(diào)停者(組長)需要維護所有與之關(guān)聯(lián)的節(jié)點(組員)的引用;通常也需要提供添加節(jié)點的功能责语;
- 在收到某個節(jié)點的消息后炮障,針對其他相關(guān)節(jié)點發(fā)送消息或調(diào)用其的某些回調(diào)方法(比如例子中給其他角色安排任務(wù))。
最后我們測試一下:
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("組長坤候,世界很大胁赢,我想去看看,請假兩天~");
}
}
張三向組長請假兩天白筹,組長收到報告后安排其他組員進行應(yīng)對:
[開發(fā)人員]張三收到來自組長的安排: 同意智末!
[開發(fā)人員]熊二收到來自組長的安排: 張三請假了,期間請接手他的開發(fā)工作徒河。
[測試人員]李四收到來自組長的安排: 張三請假了系馆,期間請將他的bug交由其他開發(fā)人員處理。
[運維人員]王五收到來自組長的安排: 張三請假了顽照,期間請將他的線上問題交由其他開發(fā)人員處理由蘑。
總結(jié)
通過回顧一下上邊的例子闽寡,我們抽取出該設(shè)計模式的關(guān)鍵點:
- 各個節(jié)點之間的網(wǎng)狀耦合關(guān)系變?yōu)橐哉{(diào)停者為中心的星型關(guān)系,其好處是明顯的尼酿,因為隨著節(jié)點數(shù)量的增加爷狈,網(wǎng)狀關(guān)系的復(fù)雜度是以階乘的速度增長的,而且網(wǎng)狀關(guān)系中一個節(jié)點的增刪都會設(shè)計到許多的改動裳擎。
- 各個節(jié)點之間不在通信涎永,全部與調(diào)停者進行雙向的通信,所以各個節(jié)點僅維護調(diào)停者的引用即可鹿响,而調(diào)停者才需要維護所有節(jié)點的引用土辩,不過注意調(diào)停者并不一定每次都要通知全部的節(jié)點。
- 通常每個節(jié)點都要有通知調(diào)停者的方法抢野,以及一個用于接收調(diào)停者消息的用于被調(diào)停者回調(diào)的方法拷淘。由于具有通用性和接口性質(zhì),這兩個方法一般放在抽象類中指孤。
調(diào)停者模式的優(yōu)點
- 松散耦合启涯。調(diào)停者模式通過把多個節(jié)點對象之間的交互封裝到調(diào)停者對象里面,從而使得節(jié)點對象之間松散耦合恃轩,基本上可以做到互補依賴结洼。這樣一來,節(jié)點對象就可以獨立地變化和復(fù)用叉跛,而不再像以前那樣“牽一處而動全身”了松忍。
- 集中控制交互。多個節(jié)點對象的交互筷厘,被封裝在調(diào)停者對象里面集中管理鸣峭,使得這些交互行為發(fā)生變化的時候,只需要修改調(diào)停者對象就可以了酥艳,當(dāng)然如果是已經(jīng)做好的系統(tǒng)摊溶,那么就擴展調(diào)停者對象,而各個節(jié)點類不需要做修改充石。
- 多對多變成一對多莫换。沒有使用調(diào)停者模式的時候,節(jié)點對象之間的關(guān)系通常是多對多的骤铃,引入調(diào)停者對象以后拉岁,調(diào)停者對象和節(jié)點對象的關(guān)系通常變成雙向的一對多,這會讓對象的關(guān)系更容易理解和實現(xiàn)惰爬。
調(diào)停者模式的缺點
調(diào)停者模式的一個潛在缺點是喊暖,過度集中化。如果節(jié)點對象的交互非常多补鼻,而且比較復(fù)雜哄啄,當(dāng)這些復(fù)雜性全部集中到調(diào)停者的時候,會導(dǎo)致調(diào)停者對象變得十分復(fù)雜风范,而且難于管理和維護咨跌。通過上邊的例子也可以初見端倪,無論是請假硼婿、建議锌半、技術(shù)分享等等所有的處理都要交給調(diào)停者,心疼組長一分鐘~