Go 設計模式 -- 中介者,最後的模式!

大家好,這裏是每週都在陪你一起進步的網管~!今天繼續學習設計模式,也是我們要學習的最後一個設計模式—中介者模式,對這個模式有一點了解後會覺得它跟我們已經學過的觀察者模式挺像,但是兩者還是有些區別的,使用場景也不一樣,具體我們放在最後再講,先來一起學習中介者模式。

中介者模式是一種行爲設計模式, 能讓程序減少對象之間混亂無序的依賴關係。 該模式會限制對象之間的直接交互, 迫使它們通過一箇中介者對象進行交互。中介者模式使修改、擴展和重用單個組件變得容易,因爲它們不再依賴於所有其他類。下面我們舉一個簡單的例子來說明怎麼在程序裏使用中介者模式減少各個組件類之間的耦合。

在現實生活中,機場的控制塔是一個典型的中介者角色, 飛機在起飛和降落前都會向控制塔發出問詢,控制塔會給飛機發送指令協調它們的起飛降落時間,避免造成事故。

現在假設一個機場只有一條跑道,即同一時刻只能承載一架飛機的起飛和降落,飛機和飛機之間不能直接溝通,這樣就亂套了,必須由控制塔作爲一箇中介者向各個飛機(組件)同步跑道的可用狀態。下面我們先來定義飛機和指揮塔 -- 即組件和中介者的 Interface 接口。

"本文使用的完整可運行源碼
去公衆號「網管叨bi叨」發送【設計模式】即可領取"
// 中介者--機場指揮塔的接口定義
type mediator interface {
 canLanding(airplane airplane) bool
 notifyAboutDeparture()
}

// 組件--飛行器的接口定義
type airplane interface {
 landing()
 takeOff()
 permitLanding()
}

接下來我們來實現具體的組件,這裏提供兩個組件作爲演示,一架波音飛機和一架空客飛機。

每個飛機在降落landing方法裏都會去跟作爲中介者的指揮塔發出問詢,看是否能夠降落,如果跑道正在被佔用,那麼會等待指揮塔調用它自己的permitLanding()通知可以降落後再降落。而其他佔用跑道的飛機在起飛後會通過中介者提供的notifyAboutDeparture() 告知指揮塔自己的離去。

具體的代碼如下:

"本文使用的完整可運行源碼
去公衆號「網管叨bi叨」發送【設計模式】即可領取"
// 組件1--波音飛機
type boeingPlane struct {
 mediator
}

func (b *boeingPlane) landing() {
 if !b.mediator.canLanding(b) {
  fmt.Println("Airplane Boeing: 飛機跑到正在被佔用,無法降落!")
  return
 }
 fmt.Println("Airplane Boeing: 已成功降落!")
}

func (b *boeingPlane)takeOff() {
 fmt.Println("Airplane Boeing: 正在起飛離開跑道!")
 b.mediator.notifyAboutDeparture()
}

func (b *boeingPlane)permitLanding() {
 fmt.Println("Airplane Boeing: 收到指揮塔信號,允許降落,正在降落!")
 b.landing()

}

// 組件2--空客飛機
type airBusPlane struct {
 mediator mediator
}

func (airbus *airBusPlane) landing() {
 if !airbus.mediator.canLanding(airbus) {
  fmt.Println("Airplane AirBus: 飛機跑到正在被佔用,無法降落!")
  return
 }
 fmt.Println("Airplane AirBus: 已成功降落!")
}

func (airbus *airBusPlane) takeOff() {
 fmt.Println("Airplane AirBus: 正在起飛離開跑道!")
 airbus.mediator.notifyAboutDeparture()
}

func (airbus *airBusPlane)permitLanding() {
 fmt.Println("Airplane AirBus: 收到指揮塔信號,允許降落,正在降落!")
 airbus.landing()
}

作爲中介者的指揮塔,提供兩個方法

"本文使用的完整可運行源碼
去公衆號「網管叨bi叨」發送【設計模式】即可領取"
// 中介者實現--指揮塔
type manageTower struct {
 isRunwayFree bool
 airportQueue []airplane
}

func (tower *manageTower) canLanding(airplane airplane) bool {
 if tower.isRunwayFree {
  // 跑道空閒,允許降落,同時把狀態變爲繁忙
  tower.isRunwayFree = false
  return true
 }
 // 跑道繁忙,把飛機加入等待通知的隊列
 tower.airportQueue = append(tower.airportQueue, airplane)
 return false
}

func (tower *manageTower) notifyAboutDeparture() {
 if !tower.isRunwayFree {
  tower.isRunwayFree = true
 }
 if len(tower.airportQueue) > 0 {
  firstPlaneInWaitingQueue := tower.airportQueue[0]
  tower.airportQueue = tower.airportQueue[1:]
  firstPlaneInWaitingQueue.permitLanding()
 }
}

func newManageTower() *manageTower {
 return &manageTower{
  isRunwayFree: true,
 }
}

這樣我們就可以通過指揮塔,協調多個飛機使用飛機場跑道進行有序的起飛和降落了。

"本文使用的完整可運行源碼
去公衆號「網管叨bi叨」發送【設計模式】即可領取"
func main() {
 tower := newManageTower()
 boeing := &boeingPlane{
  mediator: tower,
 }
 airbus := &airBusPlane{
  mediator: tower,
 }
 boeing.landing()
 airbus.landing()
 boeing.takeOff()
}

執行程序後,會有類似下面的輸出:

本文的完整源碼,已經同步收錄到我整理的電子教程裏啦,可向我的公衆號「網管叨 bi 叨」發送關鍵字【設計模式】領取。

看完例子對中介者模式有了一定了解後我們接下來再詳細說說它的構成以及用代碼實現中介者模式的步驟。

中介者模式的構成

中介者模式的結構構成可以用下面的 UML 類圖來表示圖中的各個類的構成跟我們上面代碼實例中列舉的十分類似,Component 實現類裏需要持有指向中介者的引用,中介者裏也保有對各個組件對象的引用,只不過示例裏是把組件保存在一個列表裏,UML 中是把各個組件單獨保存在了中介者的屬性裏。

下面我們再把用代碼實現中介者模式的步驟簡單敘述一遍:

  1. 定義一組會相互調用,擁有強耦合的組件。

  2. 指定中介者接口以及中介者與各個組件之間的通信方式。在大多數情況下中介者接口中必須有一個 Notify/Notification 方法從組件接收通知。

  3. 創建具體中介者實現,該實現將會存儲其管理的所有 Component 對象的引用

  4. 組件對象應該保存中介者的引用,如果想在不同上下文下使用不同的中介者實現,那麼應該通過中介者接口類型保存對具體中介者的引用。

  5. 將組件對象調用其他組件對象的方法提煉到中介者中,組件對象調用中介者的通知方法,由中介者再去調用相對應的組件的方法,從而完成組件與組件間的解耦。

中介模式與觀察者模式區別

中介模式與觀察者模式在結構上有些相似,觀察者模式中的 EventDispatcher 和 中介模式中的 Mediator 看起來很想,都是把多個組件之間的關係,維護到自身,實現組件間的間接通信達到解構效果,不過這兩個設計模式在使用場景或者叫要解決的問題上,還是有些差別

總結

中介者模式(Mediator Pattern)又叫作調解者模式或調停者模式。 用一箇中介對象封裝一系列對象交互, 中介者使各對象不需要顯式地相互作用, 從而使其耦合鬆散, 而且可以獨立地改變它們之間的交互, 屬於行爲型設計模式。

中介者模式主要適用於以下應用場景。

中介者模式的優點

中介者模式的缺點

最後

今天這篇完結後,用 Go 學設計模式就正式更新完了,算是一個小小的成就,大家可以在專輯鏈接裏查看系列裏的其他文章,後面會寫篇總結把設計模式的學習心法給大家說一說,其實就是多看,多練,除此之外也有點小技巧,咱們放到後面給系列收尾時再說。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/91-XUm5Gn9oQLd_F4dLb3A