一、引出模式
在生活中,當電腦缺少了一塊主板,那會怎么樣?如果有人這樣問我的話,我就會馬上跳出來說“這電腦肯定報廢了”,當然這不是重點。假如少了主板電腦還可以用的話,想想,里面的CPU、顯卡、聲卡、光驅、硬盤等等,不是就要我們自己用線把它們連起來。想想就覺得頭疼,那么現在你覺得主板在電腦里扮演着什么角色呢?
在軟件的開發過程中,勢必會碰到這樣一種情況,多個類或多個子系統相互交互,而且交互很繁瑣,導致每個類都必須知道他需要交互的類,這樣它們的耦合會顯得異常厲害。牽一發而動全身又不木有啊!
好了,既然問題提出來了,那有請我們這期的主角——中介者模式出場吧!
二、認識模式
1.模式定義
用一個中介者對象來封裝一系列的對象交互。中介者使得各對象不需要顯式地相互引用,從而使其松散耦合,而且可以獨立地改變它們之間的交互。
2.解決思路
出現問題的根本原因在於多個對象需要相互交互,導致了緊密耦合,不利於對象的修改和維護。
中介者模式的解決思路,就和“主板”一樣,通過引入一個中介對象,讓其他對象都只和這個中介者對象交互,而中介者對象是知道怎樣和其他對象交互。這樣一來,對象直接的交互關系就沒有了,從而實現了松散耦合。
對於中介對象而言,所有相互交互的對象,都視為同事類,中介對象就是用來維護各個同事對象之間的關系,所有的同事類都只和中介對象交互,也就是說,中介對象是需要知道所有的同事對象的。
當一個同事對象自身發生變化時,它是不知道會對其他同事對象產生什么影響,它只需要通知中介對象,“我發生變化了”,中介對象會去和其他同事對象進行交互的。這樣一來,同事對象之間的依賴就沒有了。
有了中介者之后,所有的交互都封裝在了中介對象里面,各個對象只需要關心自己能做什么就行,不需要再關系做了之后會對其他對象產生什么影響,也就是無需再維護這些關系了。
3.模式結構
Mediator:中介者接口。在里面定義了各個同事之間相互交互所需要的方法,可以是公共的方法,如Change方法,也可以是小范圍的交互方法。
ConcreteMediator:具體的中介者實現對象。它需要了解並為維護每個同事對象,並負責具體的協調各個同事對象的交互關系。
Colleague:同事類的定義,通常實現成為抽象類,主要負責約束同事對象的類型,並實現一些具體同事類之間的公共功能,比如,每個具體同事類都應該知道中介者對象,也就是每個同事對象都會持有中介者對象的引用,這個功能可定義在這個類中。
ConcreteColleague:具體的同事類,實現自己的業務,需要與其他同事對象交互時,就通知中介對象,中介對象會負責后續的交互。
4.模式示例代碼
internal class Program { private static void Main(string[] args) { } } /// <summary> /// 同事類的抽象父類 /// </summary> public abstract class Colleague { /// <summary> /// 持有中介者對象,每一個同事類都知道它的中介者對象 /// </summary> private Mediator mediator; /// <summary> /// 構造方法,傳入中介者對象 /// </summary> /// <param name="mediator">中介者對象</param> protected Colleague(Mediator mediator) { this.mediator = mediator; } /// <summary> /// 獲取當前同事類對應的中介者對象 /// </summary> /// <returns>對應的中介者對象</returns> public Mediator GetMediator() { return mediator; } } /// <summary> /// 具體的同事類A /// </summary> public class ConcreteColleagueA : Colleague { /// <summary> /// 調用父類的構造函數 /// </summary> /// <param name="mediator"></param> public ConcreteColleagueA(Mediator mediator):base(mediator) { } /// <summary> /// 執行某些業務功能 /// </summary> public void SomeOperation() { //在需要跟其他同事通信的時候,通知中介者對象 base.GetMediator().Change(this); } } /// <summary> /// 具體的同事類B /// </summary> public class ConcreteColleagueB : Colleague { /// <summary> /// 調用父類的構造函數 /// </summary> /// <param name="mediator"></param> public ConcreteColleagueB(Mediator mediator) : base(mediator) { } /// <summary> /// 執行某些業務功能 /// </summary> public void SomeOperation() { //在需要跟其他同事通信的時候,通知中介者對象 base.GetMediator().Change(this); } } /// <summary> /// 中介者,定義各個同事對象通信的接口 /// </summary> public interface Mediator { void Change(Colleague colleague); } /// <summary> /// 具體的中介者實現 /// </summary> public class ConcreteMediator { /// <summary> /// 持有並維護同事A /// </summary> private ConcreteColleagueA colleagueA; /// <summary> /// 持有並維護同事B /// </summary> private ConcreteColleagueB colleagueB; /// <summary> /// 設置中介者需要了解並維護的同事A對象 /// </summary> /// <param name="colleague">同事A對象</param> public void SetConcreteColleagueA(ConcreteColleagueA colleague) { colleagueA = colleague; } /// <summary> /// 設置中介者需要了解並維護的同事B對象 /// </summary> /// <param name="colleague">同事B對象</param> public void SetConcreteColleagueB(ConcreteColleagueB colleague) { colleagueB = colleague; } public void Changed(Colleague colleague) { //某個同事類發生了變化,通常需要與其他同事交互 //具體協調相應的同事對象來實現協作行為 } }
三、理解模式
1.中介者模式的功能
中介者的功能就是封裝對象之間的交互。如果一個對象的操作會引起其他相關對象的變化,而這個對象又不希望自己來處理這些關系,那么就可以去找中介者,讓它來處理這些麻煩的關系。
2.需要Mediator接口嗎?
這一問題,首先得弄清楚接口用來干嘛的?接口是用來“封裝隔離”的,Mediator接口用來封裝中介者對象,使得中介者對象的客戶對象跟具體的中介者實現對象分離開。
如果只有一個中介者對象,預期中也沒打算擴展,那就可以不定義Mediator接口。
3.同事關系
在標准的中介者模式中,將使用中介者對象來交互的那些對象稱為同事類,這是要求這些類都要繼承自相同的類,也就是說它們算是兄弟對象。
4.同事和中介者的關系
在模式中,當一個同事對象發生變化時,需要主動通知中介者,讓中介者去處理和其他同事對象的相關交互。
這就導致同事對象與中介對象必須有關系,同事對象需要知道中介對象,以便通知;中介對象需要知道同事對象,一邊交互。所有它們的關系是相互依賴的。
5.如何實現同事和中介者的通信
一種方式是在Mediator接口中定義一個特殊的通知接口,作為一個通用的方法,讓各個同事來調用這個方法。
另一種就是采用觀察者模式,把Mediator實現成為觀察者,各個同事對象實現為Subject.這些同事對象發生改變時,就會通知Mediator。
6.廣義中介者
在實際開發中,經常會簡化中介者模式,來是開發變得簡單,比如有如下的簡化。
l 通常會去掉同事對象的父類,這樣可以讓任意的對象,只需要有交互,就可以成為同事
l 通常不定義Mediator接口,把具體的中介者對象實現成為單例
l 同事對象不再持有中介者,而是在需要的時候直接獲取中介者對象並調用;中介者也不再持有同事對象,而是在具體處理方法里面去創建,或獲取,或從數據傳入需要的同事對象。
經過這樣的簡化、變形的情況稱為廣義中介者。
7.何時選用中介者模式
如果一組對象之間的通信方式比較復雜,導致相互依賴,結構混亂,可以采用中介者模式
如果一個對象引用很多對象,並且跟這些對象交互,導致難以服用該對象
8.模式本質
中介者模式的本質在於“封裝交互”
中介者模式的目的,就是封裝多個對象的交互,這些交互的多在中介者對象里實現。
只要是實現封裝對象的交互,就可以使用中介者模式,不必拘泥於模式結構。
9.相關模式
中介者模式和外觀模式
外觀模式多用於封裝一個子系統內部的多個模塊,目的是向子系統外部提供簡單易用的接口。
中介者模式是提供多個平等的同事對象之間交互關系的封裝,一般是用在內部實現上。
中介者模式和觀察者模式
這兩個模式可以組合使用。
中介者模式可以組合使用觀察者模式,來實現當同事對象發生改變時,通知中介對象,讓中介對象去進行與其他相關對象的交互。
最后是一個中介者模式的實例:打開電腦看電影
比如有如下的交互:
1.首先光驅讀取光盤上的數據,然后告訴主板,它的狀態已經改變了。
2.主板得到光驅的數據,將數據交給CPU進行分析處理。
3.CPU處理完,將數據分成了視頻數據和音頻數據,通知主板,已將處理好了。
4.主板得到數據,將數據交給顯卡和聲卡進行輸出。
代碼示例:
class Program { static void Main(string[] args) { //1.創建中介者——主板對象 ConcreteMediator mediator = new ConcreteMediator(); //2.創建同事類 CDDriver cd = new CDDriver(mediator); CPU cpu = new CPU(mediator); VideoCard videocard = new VideoCard(mediator); SoundCard soundcard = new SoundCard(mediator); //3.讓中介知道所有的同事 mediator.SetCDDriver(cd); mediator.SetCPU(cpu); mediator.SetVideoCard(videocard); mediator.SetSoundCard(soundcard); //4.開始看電影 cd.ReadCD(); Console.Read(); } } #region 同事抽象類 /// <summary> /// 同事抽象類 /// </summary> public abstract class Colleague { //持有中介者對象的引用,因為每個同事類都應該知道中介者對象 private Mediator mediator; //構造函數,傳入中介者對象 public Colleague(Mediator mediator) { this.mediator = mediator; } //得到當前同事類的中介者對象 public Mediator GetMediator() { return this.mediator; } } #endregion #region 同事具體類 /// <summary> /// 光驅類 /// </summary> public class CDDriver : Colleague { public CDDriver(Mediator mediator) : base(mediator) { } /// <summary> /// 光驅讀取出來的數據 /// </summary> private string Data = null; /// <summary> /// 獲取光驅讀取出來的數據 /// </summary> /// <returns></returns> public string GetData() { return this.Data; } public void ReadCD() { //逗號前是視頻數據,逗號后是聲音數據 this.Data = "這是視頻數據,這是聲音數據"; //通知主板,自己的狀態反生了改變 this.GetMediator().Change(this); } } /// <summary> /// CPU類 /// </summary> public class CPU : Colleague { public CPU(Mediator mediator) : base(mediator) { } /// <summary> /// 分解出來的視頻數據 /// </summary> private string videioData = null; /// <summary> /// 分解出來的視頻數據 /// </summary> private string soundData = null; /// <summary> /// 獲取分解出來的視頻數據 /// </summary> /// <returns></returns> public string GetVideioData() { return this.videioData; } /// <summary> /// 獲取分解出來的聲音數據 /// </summary> /// <returns></returns> public string GetSoundData() { return this.soundData; } /// <summary> /// 處理數據 /// </summary> public void ExecuteData(string data) { string[] ss = data.Split(','); this.videioData = ss[0]; this.soundData = ss[1]; //通知主板,CPU工作已完成 this.GetMediator().Change(this); } } /// <summary> /// 顯卡類 /// </summary> public class VideoCard : Colleague { public VideoCard(Mediator mediator) : base(mediator) { } /// <summary> /// 顯示視頻數據源 /// </summary> public void ShowData(string data) { Console.WriteLine("您正在看:" + data); } } /// <summary> /// 聲卡類 /// </summary> public class SoundCard : Colleague { public SoundCard(Mediator mediator) : base(mediator) { } /// <summary> /// 顯示聲音數據源 /// </summary> public void ShowData(string data) { Console.WriteLine("您正在聽:" + data); } } #endregion #region 中介者抽象類 /// <summary> /// 中介者抽象類 /// </summary> public abstract class Mediator { public abstract void Change(Colleague colleague); } #endregion #region 中介者具體類 /// <summary> /// 中介者具體類 /// </summary> public class ConcreteMediator : Mediator { private CDDriver cdDriver; private CPU cpu; private VideoCard video; private SoundCard sound; public void SetCDDriver(CDDriver cdDriver) { this.cdDriver = cdDriver; } public void SetCPU(CPU cpu) { this.cpu = cpu; } public void SetVideoCard(VideoCard video) { this.video = video; } public void SetSoundCard(SoundCard sound) { this.sound = sound; } public override void Change(Colleague colleague) { if (colleague == cdDriver) { openCDAndReadData((CDDriver)colleague); } else if (colleague == cpu) { OpenCPU((CPU)colleague); } } /// <summary> /// 打開CD,並讀取數據 /// </summary> /// <param name="cs"></param> private void openCDAndReadData(CDDriver cs) { //獲取光驅讀取的數據 string data = cdDriver.GetData(); //把這些數據傳遞給CPU進行處理 this.cpu.ExecuteData(data); } /// <summary> /// CPU處理 /// </summary> /// <param name="cpu"></param> private void OpenCPU(CPU cpu) { //獲取數據 string videoData = cpu.GetVideioData(); string soundData = cpu.GetSoundData(); //顯示數據 this.video.ShowData(videoData); this.sound.ShowData(soundData); } } #endregion