我的Java設計模式-中介者模式

Jet啟思發表於2018-01-17

*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

小時候鍾愛戰爭片,《地道戰》、《雞毛信》、《鐵道游擊隊》一系列的老電影,咦~想起都激動得起雞皮疙瘩。不過覺得特別逗的是,電影裡面總會有“這裡是xxx,我們被包圍了,請求支援請求支援”這麼一句臺詞。

來分析一下這句臺詞怎麼來的。假設有N多個戰區,戰區的分佈錯綜複雜,很多時候一個戰區的丟失會影響整個戰爭局勢。所以這就得要有一個司令部指揮和協調各個戰區,而一旦戰區被攻打,報告司令部請求支援,司令部則排程其他戰區進行協助。

那在我們的程式設計中有沒有這樣的模式?有的,中介者模式應運而生,目的就是處理這樣的情景問題。

一、中介者模式

定義

  中介者封裝一系列物件相互作用,使得這些物件耦合鬆散,並且可以獨立的改變它們之間的互動。

UML

中介者模式UML圖

中介者模式涉及到的角色有四個:

- 抽象中介者角色:抽象中介者角色定義統一的介面,以及一個或者多個事件方法,用於各同事角色之間的通訊。

- 具體中介者角色:實現了抽象中介者所宣告的事件方法,協調各同事類之間的行為,持有所有同事類物件的引用。

- 抽象同事類角色:定義了抽象同事類,持有抽象中介者物件的引用。

- 具體同事類角色:繼承抽象同事類,實現自己業務,通過中介者跟其他同事類進行通訊。

二、中介者模式實戰

假設這樣的一個情景。有A、B、C三個戰區,A被敵方攻打,請求B支援。但是此時B也被敵方攻打,所以A繼續向C請求支援,這麼巧C此時正在支援B。情景比較簡單,我們的例子也圍繞著這個情景來展開,首先來看不使用中介者模式是怎麼實現的。

A戰區程式碼如下:

public class SituationA {
	// 請求支援
	public void requestSupport(String situation) {
		System.out.println(getClass().getSimpleName() + ":這裡是A戰區,現在被敵方攻打,請求" + situation + "支援");
	}
}
複製程式碼

SituationA定義了請求支援的方法,向其他戰區請求支援。再來看B戰區的程式碼定義:

public class SituationB {
	// 請求支援
	public void requestSupport(String situation) {
		System.out.println(getClass().getSimpleName() + ":這裡是B戰區,現在被敵方攻打,請求" + situation + "支援");
	}

	// 是否支援
	public void support(boolean isSupport) {
		if (isSupport) {
			System.out.println(getClass().getSimpleName() + ":Copy that,還有五秒鐘到達戰場");
		} else {
			System.out.println(getClass().getSimpleName() + ":支援你妹,我也正在被攻打");
		}
	}
}
複製程式碼

SituationB也定義了請求支援的方法,還多了根據isSupport是否支援其他戰區的方法。還有SituationC,SituationC和SituationB程式碼差不多,直接貼出來了,不多做解釋。

public class SituationC {
	// 請求支援
	public void requestSupport(String situation) {
		System.out.println(getClass().getSimpleName() + ":這裡是B戰區,現在被敵方攻打,請求" + situation + "支援");
	}

	// 是否支援
	public void support(boolean isSupport) {
		if (isSupport) {
			System.out.println(getClass().getSimpleName() + ":Copy that,還有五秒鐘到達戰場");
		} else {
			System.out.println(getClass().getSimpleName() + ":不好意思,來遲一步了,正在前往別的戰區支援");
		}
	}
}
複製程式碼

OK,三個類都定義好了,我們根據情景看看客戶端是怎樣執行的,程式碼如下:

public class Client {
	public static void main(String[] args) {
		System.out.println("-------A被攻打,請求B支援--------");
		SituationA situationA = new SituationA();
		situationA.requestSupport("B");
		System.out.println("-------B也正在被攻打--------");
		SituationB situationB = new SituationB();
		situationB.support(false);
		System.out.println("-------A又向C請求支援--------");
		situationA.requestSupport("C");
		System.out.println("-------C很忙--------");
		SituationC situationC = new SituationC();
		situationC.support(false);
	}
}
複製程式碼

執行結果如下:

-------A被攻打,請求B支援--------

SituationA:這裡是A戰區,現在被敵方攻打,請求B支援

-------B也正在被攻打--------

SituationB:支援你妹,我也正在被攻打

-------A又向C請求支援--------

SituationA:這裡是A戰區,現在被敵方攻打,請求C支援

-------C很忙--------

SituationC:不好意思,來遲一步了,正在前往別的戰區支援

回到我們的場景當中,A、B、C是相互兩兩關聯的,並且關聯的兩個類與其他類是不能協調通訊。因此,在實際中,戰區類增多,它們之間的耦合度越高,這樣首先會造成當一個類修改了,其他類也要跟著需要修改,然後就是多個類之間的通訊變得複雜混亂。就跟我們上面列子一樣,A、B、C相互支援,A並不知道C已經支援B了。因為這些原因,在程式碼設計中加入中介者角色,每個類都經過中介者進行溝通和協調。

下面來看中介者模式的實現,首先定義抽象中介者角色類,程式碼如下:

public abstract class Mediator {
	protected SituationA situationA;
	protected SituationB situationB;
	protected SituationC situationC;

	Mediator() {
		situationA = new SituationA(this);
		situationB = new SituationB(this);
		situationC = new SituationC(this);
	}

	/**
	 * 事件的業務流程處理
	 *
	 * @param method
	 */
	public abstract void execute(String method);
}
複製程式碼

抽象中介者類主要定義了同事類的事件業務流程方法,並且持有每一個具體同事類的引用,再來看具體中介者的實現:

public class Command extends Mediator {

	public void execute(String method) {
		if (method.equals("aRequestSupport")) {
			this.aRequestSupport();
		} else if (method.equals("bRequestSupport")) {
			this.bRequestSupport();
		}
	}

	// A請求支援
	private void aRequestSupport() {
		System.out.println("SituationA:這裡是A戰區,現在被敵方攻打,請求支援");
		boolean isBSupport = isSupport();  // B是否可以支援
		super.situationB.bSupport(isBSupport);
		if (!isBSupport) { // B支援不了,請求C
			System.out.println("-------A又向C請求支援--------");
			boolean isASupport = isSupport();  // B是否可以支援
			super.situationC.cSupport(isASupport);
			if (!isASupport) {
				System.out.println("-------自己看著辦吧。--------");
			}
		}
	}

	// B請求支援
	public void bRequestSupport() {
		System.out.println("這裡是B的請求支援");
	}

	private boolean isSupport() {
		Random rand = new Random();
		return rand.nextBoolean();
	}
}
複製程式碼

程式碼比較長,但也比較簡單。定義了處理各個物件關係的業務方法,把依賴關係轉移到了這個業務方法中,而同事類只需要委託中介者協調各個同事類的業務邏輯。

public abstract class Colleague {
	protected Mediator mediator;

	public Colleague(Mediator mediator) {
		this.mediator = mediator;
	}
}
複製程式碼

很簡單的就一個構造方法,繼續看具體同事類的實現,我先把各個同事類的程式碼都貼出來:

// A戰區
public class SituationA extends Colleague {

	public SituationA(Mediator mediator) {
		super(mediator);
	}

	// 請求支援
	public void aRequestSupport() {
		super.mediator.execute("aRequestSupport");
	}
}

// B戰區
public class SituationB extends Colleague {

	public SituationB(Mediator mediator) {
		super(mediator);
	}

	// 請求支援
	public void bRequestSupport() {
		super.mediator.execute("bRequestSupport");
	}

	public void bSupport(boolean isSupport) {
		if (isSupport) {
			System.out.println("SituationB:Copy that,還有五秒鐘到達戰場");
		} else {
			System.out.println("-------B也正在被攻打--------");
			System.out.println("SituationB:支援你妹,我也正在被攻打");
		}
	}
}

// C戰區
public class SituationC extends Colleague {
	public SituationC(Mediator mediator) {
		super(mediator);
	}

	// 請求支援
	public void cRequestSupport() {
		super.mediator.execute("cRequestSupport");
	}

	public void cSupport(boolean isSupport) {
		if (isSupport) {
			System.out.println(getClass().getSimpleName() + ":Copy that,還有五秒鐘到達戰場");
		} else {
			System.out.println(getClass().getSimpleName() + ":不好意思,來遲一步了,正在前往別的戰區支援");
		}
	}
}
複製程式碼

跟前面說的一樣,通過cRequestSupport方法中的execute委託中介者處理同事類的業務邏輯,本身只負責處理自身的業務。

最後來看客戶端的實現,程式碼如下:

public class Client {
	public static void main(String[] args) {
		Mediator mediator = new Command();
		System.out.println("-------A被攻打,請求支援--------");
		SituationA situationA = new SituationA(mediator);
		situationA.aRequestSupport();
	}
}
複製程式碼

可以看到,表面上請求還是從A發出,但是A已經委託了中介者進行業務邏輯和流程的處理。這樣的好處就是每個同事類的職責都很清晰,跟其他同事類有關聯的都委託到中介者,本身專注自己的行為。

執行客戶端,結果如下:

-------A被攻打,請求支援--------

SituationA:這裡是A戰區,現在被敵方攻打,請求支援

-------B也正在被攻打--------

SituationB:支援你妹,我也正在被攻打

-------A又向C請求支援--------

SituationC:Copy that,還有五秒鐘到達戰場

三、中介者模式的優缺點

優點

1)解耦。把同事類原來一對多的依賴變成一對一的依賴,降低同事類的耦合度,同時也符合了迪米特原則。

缺點

1)中介者模式把業務流程和協調都寫在中介者,當同事類越多,中介者的業務就越複雜,造成不好管理的弊端。

2)中介者模式還有一個明顯的缺點,如果要增減同事類,必須得修改抽象中介者角色和具體中介者角色類。

四、模式擴充套件

中介者模式和觀察者模式混編

為什麼要跟觀察者模式組合混編?首先,上面提到了如果要增加或者刪除同事類,必須對中介者這個角色進行修改,因為中介者角色的業務邏輯相對比較集中和複雜,修改中介者角色會比較麻煩。另外一點是,使用觀察者模式實現同事類(被觀察者)的通訊可以優化中介者的業務邏輯流程,避免過多使用if...else。

同事類通知->中介者協調處理->中介者通知同事類

其實可以說成中介者模式是通過觀察者模式實現的,都是事件驅動模型。這裡簡單闡述下原理,把中介者作為觀察者,即中介者角色實現Observer介面,重寫update方法(重點就在update,同事類跟中介者,中介者月同事類之間的通訊就在這實現)。同事類繼承Observable被觀察者類,通過notifyObservers可以與中介者通訊。這樣就在相當於觀察者模式的基礎上(觀察者模式的互動路徑較短),在中介者中增加了訊息轉發的功能,也就是說同事類之間的通訊經過了中介者。

中介者模式VS門面模式

先簡單介紹下門面模式。要求一個子系統的外部與其內部的通訊必須通過一個統一的物件進行,這就是門面模式。門面模式主要的是提供了一個高層次的介面,也就是所謂的一個統一物件,通過它跟子系統進行通訊。這樣做的好處就是,第一,外部與系統內部解耦,減少相互之間的依賴;第二,增加系統內部的靈活性,系統內部變化不影響外部跟門面角色的關係。比如拍照,可以用手機拍,也可以用單反相機拍,把手機和單反封裝在一起,外部只能看到一個攝像頭,你只能說拍照,裡面到底是手機拍還是單反拍是不知道的。

中介者模式和門面模式同樣的都是通過封裝一個角色來進行隔離解耦,但中介者強調的是中介協調同事類之間的通訊,門面模式則是通過門面對內部進行隔離。另外,中介者模式的通訊是雙向的,而門面模式的通訊是單向的。

總結

系統中多個物件之間相互依賴,並且依賴關係結構複雜導致物件之間的耦合度增大,修改難度大,這個時候可以考慮使用中介者模式來梳理物件之間的通訊,達到降低物件之間耦合度的效果。中介者模式就到這,下一篇命令模式,您的點贊和關注是我的動力,欽敬欽敬!

設計模式Java原始碼GitHub下載https://github.com/jetLee92/DesignPattern

AndroidJet的開發之路

相關文章