Mediator 模式設計 | 實現與解說 | 物件導向設計

Mediator 模式設計 | 實現與解說 | 物件導向設計

Overview of Content

在軟體設計中,Mediator 模式是一種行為型設計模式,旨在解決多個物件之間的相互作用與耦合度過高的問題。Mediator 模式將多個物件之間的相互作用集中到一個中介者(Mediator)對象中,從而使各個物件之間的耦合度降低,增加了彼此之間的彈性與可維護性。

這種模式的使用場景、定義及 UML 圖、設計優缺點以及實際應用都是我們在以下章節中所要深入探討的重點


Mediator 設計概述

我們在程式設計時往往會收到許多有嚴重相依性的類,這些類有嚴重的耦合關係,密不可分;以類的關係圖來說就是一個類關聯、依賴另一個類,形成了複雜關係(像是三、四角關係,相互關聯)

也就是說,我們往往可以在複雜的類設計中發現,這些設計不符合 迪米特原則(最少知識原則);而Mediator 設計就是解決這個問題的方案之一

Mediator 使用場景

● MVC 架構,其中的 C 層級 (Controller) 就是一個仲介者,調和 View & Model 之間的通訊

● Mediator 模式可以使用在有強耦合類之間,這些耦合關係在類圖中看起來就像是一個蜘蛛網結構

在物件導向的程式中,物件與物件的依賴關係是必然的(如果沒有依賴關係,那基本上這個類不需要了~)

拓展:網路、藍芽拓撲圖

一般來說網路、藍芽連線拓樸有三種類型,1. 總線型、2. 環形、3. 星形;而 仲介者模式是和處理的則是「星形」拓樸

## 總線型
o--o--o--o--o

## 環形
  o
 / \
o   o
 \ /
  o

## 星形(Mediator 可以作用於中間的點)

   o
   |
o--o--o
   |
   o

Mediator 定義 & UML

● Mediator 定義:用一個仲介對象來封裝 (encapsulate) 不同物件之間的溝通,使其變成鬆耦合關係,並且可以獨立改變他們的交互

● Mediator UML 角色關係

角色說明
Mediator (抽象)仲介者的統一方法,調用該方法就可以執行對應的功能
ConcreteMeditor具體中介,透過調用個個實做 Colleague 來達成目標功能
Colleague具體的功能實作,主要可以把該類的函數分為兩種,1. Self-Method 改變自身狀態、2. Dep-Method 依賴方法

Mediator 設計優缺點

● Mediator 定義 & Mediator UML 設計

全部改成依賴 Mediator 的抽象核心,這是為了讓其滿足 依賴倒置

● Mediator 缺點:Mediator 抽象類的成員會依照需求手動增加,本身不符合開閉原則,必須依照需求不斷地去修改其(ConcreteMediator)功能,並且邏輯複雜

Mediator 成員越多邏輯越複雜


Mediator 實現

接下來我們來實現 Mediator 標準實現、Mediator 抽象化

Mediator 標準實現

A. Colleague:各自有自己的功能,可以分開維護,如果有要使用到別的相依類,則透過 Mediator (以下寫 3 個 Colleague 類)

class MonthSalary constructor(private val mediator: Mediator) {

    var salary: Int = 30000

    fun useMoney(used: Int) {
        if (salary <= 0) {
            return
        }

        salary -= used

        println("After useMoney, have \$$salary")
    }

    fun sickDay(days: Int) {
        println("Before sick, have \$$salary")

        salary -= days * 10
        mediator.execute(Mediator.Feat.STOP_PLAYING_READING)

        println("After sick, just have \$$salary")
    }
}

class Learning constructor(private val mediator: Mediator) {

    private val bookList : MutableList<String> = mutableListOf()
    var learningProgress = 0

    fun buyBook(name: String) {
        mediator.run {
            // 檢查薪水
            if (execute(Mediator.Feat.CHECK_SALARY) as Boolean) {
                // 買書
                execute(Mediator.Feat.USE_MONEY, 300).also {
                    // 自身邏輯
                    bookList.add(name)
                    println("Buy Book")
                }
            } else {
                println("No money to buy")
            }
        }
    }

    fun readingBook() {
        learningProgress += 10

        println("Reading... $learningProgress")
    }

    fun stopReading() {
        println("Stop reading...")
    }
}


class PlaySomething constructor(private val mediator: Mediator) {

    private var curGame : String? = null

    fun playOnlineGame(gameName: String) {
        mediator.run {
            // 檢查薪水
            if ((execute(Mediator.Feat.LEARNING_PROGRESS) as Int) < 0) {
                println("Learning first.")
                return
            }

            if (execute(Mediator.Feat.CHECK_SALARY) as Boolean) {
                // 買書
                execute(Mediator.Feat.USE_MONEY, 500).also {
                    // 自身邏輯
                    curGame = gameName

                    println("Playing game ... $gameName")
                }
            } else {
                println("No money to play.")
            }
        }
    }

    fun stopPlay() {
        curGame = null
    }
}

B. Mediator 類:該類有幾個責任,1. 對外給提供給使用者方法、2. 持有每個 Colleague 實作類

這裡的範例是依賴於各個 Colleague 實作類(不符合依賴倒置),你也可以依照設計重新規劃為抽象類

abstract class Mediator {

    enum class Feat {
        USE_MONEY,
        CHECK_SALARY,
        LEARNING_PROGRESS,
        STOP_PLAYING_READING
    }

    protected val salary = lazy {
        MonthSalary(this)
    }.value
    protected val learn = lazy {
        Learning(this)
    }.value
    protected val play = lazy {
        PlaySomething(this)
    }.value

    // 接收各個實做的操控
    abstract fun execute(feat: Feat, vararg params: Any) : Any


    // 業務邏輯
    abstract fun sick()

    abstract fun learning()

    abstract fun play()
}

C. ConcreteMeditor:真正的實作仲介類,內部使用各個類別的功能來達到使用者所需的功能

class ConcreteMediator : Mediator() {
    override fun execute(feat: Feat, vararg params: Any): Any {

        when (feat) {
            Feat.USE_MONEY -> {
                salary.useMoney(params[0] as Int)
            }
            Feat.CHECK_SALARY -> {
                return salary.salary > 0
            }
            Feat.LEARNING_PROGRESS -> {
                return learn.learningProgress
            }
            Feat.STOP_PLAYING_READING -> {
                learn.stopReading()
                play.stopPlay()
            }
        }

        return Unit
    }

    override fun sick() {
        salary.sickDay(3)
    }

    override fun learning() {
        learn.buyBook("Hello World")
        learn.readingBook()
    }

    override fun play() {
        play.playOnlineGame("Maple story")
    }

}

這裡做了個不好的示範:因為可能會造成遞歸的狀況

由於這裡仲介者也對其他的實做類發起行為,操作不當的話很有可能造成遞歸的情況!

這可以 使用測試、規則、紀錄… 等等方式避免,最好的方式是 不要讓仲介去執行內部實做類的方法
override fun execute(feat: Feat, vararg params: Any): Any {

    when (feat) {
        ... 省略部份
        Feat.STOP_PLAYING_READING -> {
            // 這裡由仲介來呼叫,可能造成遞迴
            learn.stopReading()
            play.stopPlay()
        }
    }

    return Unit
}

● 以下 User 使用 Mediator 來達到需要的功能

fun main() {
    val mediator: Mediator = ConcreteMediator()

    mediator.learning().also { println("\n") }
    mediator.sick().also { println("\n") }
    mediator.play().also { println("\n") }
}

Mediator 抽象化

● 從上面我們可以看出各個 Colleague 都會依賴於 Mediator (抽象) 類;這邊我們可以拓展讓 Colleague 繼承於抽象,如下

A. 抽象 Colleague:可以在這個類中宣告公用的方法

當然你也可以把所有 Colleague 類的方法都抽象化,但符合依賴倒置,但是違反了 單一職責

因為抽象類會承攬所有業務方法,但有子類應該只實現與自身相關的方法才對
abstract class AbstractColleague(protected val mediator: Mediator) {
    // 宣告共用方法
    abstract fun showInfo()
}

B. Colleague:實作 showInfo 共用方法

class PlaySomething constructor(mediator: Mediator) : AbstractColleague(mediator)  {

    ... 省略部分

    override fun showInfo() {
        println("curGame=($curGame)")
    }

}

class Learning constructor(mediator: Mediator) : AbstractColleague(mediator) {

    ... 省略部分

    override fun showInfo() {
        println("learningProgress=($learningProgress), list=($bookList)")
    }
}


class MonthSalary constructor(mediator: Mediator) : AbstractColleague(mediator) {

    ... 省略部分

    override fun showInfo() {
        println("salary=($salary)")
    }
}

C. Mediator 類:實作商業所需邏輯

abstract class Mediator {
    ... 省略部分


    fun showInfo() {
        salary.showInfo()
        learn.showInfo()
        play.showInfo()
    }
}

● User 使用幾本上沒有改變

fun main() {
    val mediator: Mediator = ConcreteMediator()

    mediator.learning().also { println("\n") }
    mediator.sick().also { println("\n") }
    mediator.play().also { println("\n") }

    mediator.showInfo().also { println("\n") }
}

更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式 Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式 Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響