調停者模式是對象的行為模式。調停者模式包裝了一系列對象互相作用的方式,使得這些對象不必互相明顯引用。進而便它們可以較松散地耦合。當這些對象中的某些對象之間的互相作用發生改變時,不會立即影響到其它的一些對象之間的互相作用。進而保證這些互相作用可以彼此獨立地變化。 (學習)
為什麼需要調停者
如下圖所示,這個示意圖中有大量的對象,這些對既會影響别的對象,又會被别的對象所影響,是以常常叫做同僚(Colleague)對象。這些同僚對象通過彼此的互相作用形成系統的行為。從圖中可以看出,幾乎每一個對象都需要與其它對象發生互相作用,而這種互相作用表現為一個對象與另一個對象的直接耦合。這就是過度耦合的系統。
通過引入調停者對象(Mediator),可以将系統的網狀結構變成以中介者為中心的星形結構,如下圖所示。在這個星形結構中,同僚對象不再通過直接的聯系與另一個對象發生互相作用;相反的,它通過調停者對象與另一個對象發生互相作用。調停者對象的存在保證了對象結構上的穩定,也就是說,系統的結構不會因為新對象的引入造成大量的修改工作。
一個好的面向對象的設計可以使對象之間增加協作性(Collaboration),減少耦合度(Couping)。一個深思熟慮的設計會把一個系統分解為一群互相協作的同僚對象,然後給每一個同僚對象以獨特的責任,恰當的配置它們之間的協作關系,使它們可以在一起工作。
如果沒有主機闆
大家都知道,電腦裡面各個配件之間的互動,主要是通過主機闆來完成的。如果電腦裡面沒有了主機闆,那麼各個配件之間就必順自行互相互動,以互相傳送資料。而且由于各個配件的接口不同,互相之間互動時,還必順把資料接口進行轉換才能配對得上。
所幸是有了主機闆,各個配件的互動完全通過主機闆來完成,每個配件都隻需要和主機闆互動,而主機闆知道如何跟所有的配件打交道,這樣就簡單多了。
調停者模式的結構
調停者模式的示意性類圖如下所示:
調停者模式包含以下角色:
- 抽象調停者(Mediator)角色:
定義出同僚對象到調停者對象的接口,其中主要方法是一個(或多個)事件方法。
- 具體調停者(ConcreteMediator)角色:
實作了抽象調停者所聲明的事件方法。具體調停者知曉所有的具體同僚類,并負責具體的協調各同僚對象的互動關系。
- 抽象同僚(Colleague)類角色:
定義出調停者到同僚對象的接品。同僚對象隻知道調停者而不知道其餘的同僚對象。
- 具體同僚(ConcreteColleague)類角色:
所有的具體同僚類均從抽象同僚類繼承而來,實作自已的業務,在需要與其它同僚通信的時候,就與持有的調停者通信,調停者會負責與其他的同僚互動。
源代碼
package Mediator
/**
* 抽象調停者類
* */
interface Mediator {
/**
* 同僚對象在自身改變的時候來通知調停者方法
* 讓調停者去負責相應的與其他同僚對象的互動
* */
fun changed(colleague: Colleague)
}
package Mediator
/**
* 抽象同僚類
* */
abstract class Colleague constructor(mediator: Mediator) {
//持有一個調停者對象
private var mediator: Mediator? = null
init {
this.mediator = mediator
}
/**
* 擷取目前同僚類對應的調停者對象
* */
fun getMediator(): Mediator? {
return this.mediator
}
}
package Mediator
/**
* 具體調停者類
* */
class ConcreteMediator : Mediator {
//持有并維護同僚A
var concreteColleagueA: ConcreteColleagueA? = null
//持有并維護同僚B
var concreteColleagueB: ConcreteColleagueB? = null
override fun changed(colleague: Colleague) {
/**
* 某一同僚類發生了變化,通常需要與其它同僚互動,
* 具體協調相應的同僚對象來實作協作的行為。
* */
}
}
package Mediator
/**
* 具體同僚類
* */
class ConcreteColleagueA constructor(mediator: Mediator) : Colleague(mediator) {
/**
* 示意方法,執行某些操作
* */
fun opreation(){
//在需要跟其它同僚通信的時候,通适調停者對象
getMediator()?.changed(this)
}
}
package Mediator
class ConcreteColleagueB constructor(mediator: Mediator) : Colleague(mediator) {
/**
* 示意方法,執行某些操作
* */
fun opreation(){
//在需要跟其它同僚通信的時候,通适調停者對象
getMediator()?.changed(this)
}
}
使用電腦來看電影
在日常生活中,我們經常使用電腦來看電影,把這個過程描述出來,簡化後假定會有如下的互動過程:
- 首先是光驅要讀取CD光牒上的資料,然後告訴主機闆,它的狀态改變了。
- 主機闆去得到光驅的資料,把這些資料交給cpu進行分析處理。
- cpu處理完後,把資料分成了視訊資料和音頻資料,通知主機闆,它處理完了。
- 主機闆去得到cpu處理過後的資料,分别把資料交給顯示卡和聲霸卡,去顯示視訊和發出聲音。
要使用調停者模式來實作示例,那就要區分出同僚對象和調停者對象。很明顯,主機闆是調停者,而光驅、聲霸卡、cpu、顯示等配件,都是作為同僚對象。
源代碼
package Mediator.example
/**
* 抽象同僚類
* */
abstract class Colleague constructor(mediator: Mediator) {
//持有一個調停者對象,目前同僚類對應的調停者對象
var mediator: Mediator? = null
init {
this.mediator = mediator
}
}
package Mediator.example
/**
* 同僚類-光驅
* */
class CDDriver constructor(mediator: Mediator) : Colleague(mediator) {
//光驅讀取出來的資料
var data = ""
/**
* 讀取CD光牒
* */
fun readCD() {
//逗号前是視訊顯示的資料,逗号後是聲音
data = "火影, kaka西"
//通知主機闆自已的狀态發生了改變
mediator?.changed(this)
}
}
package Mediator.example
/**
* 同僚類-cpu
* */
class CPU constructor(mediator: Mediator) : Colleague(mediator) {
//解釋出來的視訊資料
var videaData = ""
//解釋出來的聲音資料
var soundData = ""
/**
* 處理資料,把資料分成視訊和聲音資料
* */
fun executeData(data: String) {
val list = data.split(",")
videaData = list[0]
soundData = list[1]
//通知主機闆,cpu完成工作
mediator?.changed(this)
}
}
package Mediator.example
/**
* 同僚類-顯示卡
* */
class VideoCard constructor(mediator: Mediator) : Colleague(mediator) {
/**
* 顯示視訊資料
* */
fun showData(data: String) {
println("you are watching: $data")
}
}
package Mediator.example
/**
* 同僚類-聲霸卡
* */
class SoundCard constructor(mediator: Mediator) : Colleague(mediator) {
/**
* 按照視訊資料發出聲音
* */
fun soundData(data: String) {
println("sound in video is: $data")
}
}
package Mediator.example
/**
* 抽象調停者類
* */
interface Mediator {
/**
* 同僚對象在自身改變的時候來通知調停者方法,
* 讓調停者去負責相應的與其他同僚對象的互動
* */
fun changed(colleague: Colleague)
}
package Mediator.example
/**
* 具體調停者類,這裡是主機闆
* */
class MainBoard : Mediator {
//需要知道要互動的同僚類:光驅類
var cdDriver: CDDriver? = null
//需要知道要互動的同僚類:CPU類
var cpu: CPU? = null
//需要知道要互動的同僚類:顯示卡類
var videoCard: VideoCard? = null
//需要知道要互動的同僚類:聲霸卡類
var soundCard: SoundCard? = null
/**
* 處理光驅讀取資料以後,與其他對象的互動
* */
fun opeCDDriverReadData(cd: CDDriver) {
//先擷取光驅讀取的資料
val data = cd.data
//把資料交給cpu處理
cpu?.executeData(data)
}
/**
* 處理cpu處理資料後與其他對象的互動
* */
fun opeCPU(cpu: CPU) {
//先擷取cpu處理後的資料
val videoData = cpu.videaData
val soundData = cpu.soundData
//把這些資料傳遞給顯示卡和聲霸卡
videoCard?.showData(videoData)
soundCard?.soundData(soundData)
}
override fun changed(colleague: Colleague) {
if (colleague is CDDriver) {
//表示光驅讀取了資料
opeCDDriverReadData(colleague)
} else if (colleague is CPU) {
//表示cpu處理了資料
opeCPU(colleague)
}
}
}
代碼運作
//建立調停者-主機闆
val mainBoard = MainBoard()
//建立同僚類
val cdDriver = CDDriver(mainBoard)
val cpu = CPU(mainBoard)
val videoCard = VideoCard(mainBoard)
val soundCard = SoundCard(mainBoard)
//讓調停者知道所有同僚
mainBoard.cdDriver = cdDriver
mainBoard.cpu = cpu
mainBoard.videoCard = videoCard
mainBoard.soundCard = soundCard
//開始看電影,把CD光牒放入光驅
cdDriver.readCD()
運作結果
you are watching: 火影
sound in video is: kaka西
調停者模式的優化點
- 松散耦合
調停者模式通過把多個同僚對象之間的互動封裝到調停者對象裡面,進而使得同僚對象之間松散耦合,基本上可以做到互補依賴。這樣一來,同僚對象就可以獨立地變化和複用,而不再像以前那樣“牽一處而動全身”了。
- 集中控制互動
多個同僚對象的互動,被封裝在調停者對象裡面集中管理,使得這些互動行為發生變化的時候,隻需要修改調停者對象就可以了,當然如果是已經做好的系統,那麼就擴充調停者對象,而各個同僚類不需要做修改。
- 多對多變成一對多
沒有使用調停者模式的時候,同僚對象之間的關系通常是多對多的,引入調停者對象以後,調停者對象和同僚對象的關系通常變成雙向的一對多,這會讓對象的關系更容易了解和實作。
調停者模式的缺點
調停者模式的一個潛在缺點是,過度集中化。如果同僚對象的互動非常多,而且比較複雜,當這些複雜性全部集中到調停者的時候,會導緻調停者對象變得十分複雜,而且難于管理和維護。