9.3.4 الگو Mediator

9.3.4 الگو Mediator

9.3.4.1-الگوی طراحی Mediator #

الگوی طراحی واسطه (Mediator)، یک الگوی رفتاری است که به شما امکان می‌دهد تا وابستگی‌های درهم‌تنیده بین اشیاء را کاهش دهید. این الگو ارتباط مستقیم بین اشیاء را محدود می‌کند و آن‌ها را مجبور می‌سازد تا تنها از طریق یک شیء واسطه با هم همکاری کنند.

9.3.4.2-مشکل #

فرض کنید یک جعبه dialog برای ایجاد و ویرایش پروفایل کاربران دارید. این جعبه dialog شامل کنترل‌های مختلف فرم مانند فیلدهای متنی، کادرهای انتخابی(checkbox)، دکمه‌ها و غیره می‌شود.

mediator-problem1

ممکن است برخی از عناصر فرم با سایرین تعامل داشته باشند. برای مثال، انتخاب checkbox “من یک سگ دارم” ممکن است یک فیلد متنی پنهان برای وارد کردن نام سگ را نمایش دهد. مثال دیگر دکمه “ثبت” است که باید قبل از ذخیره اطلاعات، صحت مقادیر همه فیلدها را تایید کند.

mediator-problem2

با پیاده‌سازی مستقیم این منطق درون کد عناصر فرم، باعث می‌شوید استفاده‌ی مجدد از کلاس‌های این عناصر در فرم‌های دیگر برنامه بسیار دشوار شود. برای نمونه، به دلیل وابستگی به فیلد متن سگ، نمی‌توانید از کلاس کادر انتخاب مذکور در فرم دیگری استفاده کنید. در این حالت، مجبور هستید یا از تمام کلاس‌های درگیر در نمایش فرم پروفایل استفاده کنید، یا هیچ‌کدام را به کار نبرید.

9.3.4.3- راه‌حل #

الگوی طراحی Mediator پیشنهاد می‌کند که تمام ارتباط‌های مستقیم بین اجزایی که می‌خواهید مستقل از یکدیگر باشند را متوقف کنید. در عوض، این اجزا باید به صورت غیرمستقیم با هم همکاری کنند، یعنی با فراخوانی یک شیء واسطه‌ی خاص که تماس‌ها را به اجزای مناسب هدایت می‌کند. در نتیجه، اجزا تنها به یک کلاس واسطه وابسته می‌شوند، نه اینکه به ده‌ها جزء همکار دیگرشان وابسته باشند.

در مثال فرم ویرایش پروفایل، خود کلاس باکس گفتگو می‌تواند نقش واسطه را ایفا کند. به احتمال زیاد، کلاس جعبه dialog از قبل از تمام زیرمجموعه‌هایش آگاه است، بنابراین حتی نیازی به معرفی وابستگی‌های جدید به این کلاس نخواهید داشت.

mediator-solution1-en

مهم‌ترین تغییر در عناصر واقعی فرم اتفاق می‌افتد. بیایید دکمه‌ی “ثبت” را در نظر بگیریم. پیش از این، هر بار که کاربر روی دکمه کلیک می‌کرد، این دکمه مجبور بود صحت مقادیر تمام عناصر فرم مجزا را تایید کند. حالا تنها وظیفه‌ی دکمه، اطلاع‌رسانی به جعبه dialog در مورد کلیک است. جعبه dialog پس از دریافت این اطلاع‌رسانی، تایید صحت را خودش انجام می‌دهد یا این وظیفه را به عناصر مجزا واگذار می‌کند. بنابراین، به جای وابستگی به ده‌ها عنصر فرم، دکمه تنها به کلاس باکس گفتگو وابسته است.

می‌توانید فراتر بروید و وابستگی را حتی سست‌تر کنید، با این کار که یک واسط مشترک برای تمام انواع جعبه‌های dialog تعریف کنید. این واسط، متد اطلاع‌رسانی را معرفی می‌کند که همه عناصر فرم می‌توانند از آن برای اطلاع‌رسانی به باکس گفتگو در مورد رویدادهای رخ‌داده در آن عناصر استفاده کنند. بنابراین، دکمه‌ی «ثبت» ما حالا باید بتواند با هر جعبه dialog که آن واسط را پیاده‌سازی می‌کند، کار کند.

به این ترتیب، الگوی طراحی واسطه به شما امکان می‌دهد تا یک شبکه‌ی پیچیده‌ی روابط بین اشیاء مختلف را درون یک شیء واسطه‌ی واحد کپسوله‌سازی کنید. هرچه وابستگی‌های یک کلاس کمتر باشد، اصلاح، توسعه یا استفاده‌ی مجدد از آن کلاس آسان‌تر می‌شود.

9.3.4.4- تشبیه در دنیای واقعی #

mediator-live-example

خلبانان هواپیماهایی که به منطقه‌ی کنترل فرودگاه نزدیک می‌شوند یا از آن خارج می‌شوند، به طور مستقیم با یکدیگر ارتباط برقرار نمی‌کنند. در عوض، آن‌ها با یک کنترل‌کننده‌ی ترافیک هوایی صحبت می‌کنند که در یک برج بلند، جایی در نزدیکی باند فرودگاه قرار دارد. بدون وجود کنترل‌کننده ترافیک هوایی، خلبانان باید از هر هواپیمایی در حوالی فرودگاه آگاه باشند و با یک کمیته‌ی متشکل از ده‌ها خلبان دیگر در مورد اولویت‌های فرود بحث کنند. این امر احتمالا آمار سقوط هواپیما را به طرز چشمگیری افزایش می‌داد.

برج نیازی به کنترل کل پرواز ندارد. برج فقط برای اعمال محدودیت‌ها در منطقه‌ی فرودگاه وجود دارد.

9.3.4.5- مثال #

همانطور که می‌دانیم؛ الگوی طراحی Mediator یک الگوی طراحی رفتاری است. این الگو پیشنهاد می کند برای جلوگیری از ارتباط مستقیم بین اشیاء، یک شیء میانجی ایجاد شود تا وابستگی های مستقیم بین آنها از بین برود.

یک مثال بسیار خوب از الگوی Mediator، سکوی سیستم راه آهن است. دو قطار هرگز برای در دسترس بودن سکو با یکدیگر ارتباط برقرار نمی‌کنند. مسئول ایستگاه (stationManager) به عنوان میانجی (Mediator) عمل می کند و سکو را فقط برای یکی از قطارها در دسترس قرار می‌دهد. قطار با مسئول ایستگاه (stationManager) ارتباط برقرار می‌کند و بر اساس دستورات آن عمل می‌کند. این الگو صفی از قطارهای در انتظار را مدیریت می کند. در صورت خروج هر قطاری از سکو، به یکی از قطارها اطلاع می دهد که در ادامه به سکو برسد.

توجه کنید که چگونه stationManager در کد زیر به عنوان میانجی بین trains و platform عمل می کند.

  • passengerTrain و goodsTrain رابط train را پیاده سازی می‌کنند.
  • stationManager رابط mediator را پیاده سازی می‌کند.

9.3.4.6- مثال کاربردی #

train.go

package main

type train interface {
    requestArrival()
    departure()
    permitArrival()
}

passengerTrain.go

package main

import "fmt"

type passengerTrain struct {
    mediator mediator
}

func (g *passengerTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("PassengerTrain: Landing")
    } else {
        fmt.Println("PassengerTrain: Waiting")
    }
}

func (g *passengerTrain) departure() {
    fmt.Println("PassengerTrain: Leaving")
    g.mediator.notifyFree()
}

func (g *passengerTrain) permitArrival() {
    fmt.Println("PassengerTrain: Arrival Permitted. Landing")
}

goodsTrain.go

package main

import "fmt"

type goodsTrain struct {
    mediator mediator
}

func (g *goodsTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("GoodsTrain: Landing")
    } else {
        fmt.Println("GoodsTrain: Waiting")
    }
}

func (g *goodsTrain) departure() {
    g.mediator.notifyFree()
    fmt.Println("GoodsTrain: Leaving")
}

func (g *goodsTrain) permitArrival() {
    fmt.Println("GoodsTrain: Arrival Permitted. Landing")
}

mediator.go

package main

type mediator interface {
    canLand(train) bool
    notifyFree()
}

stationManager.go

package main

import "sync"

type stationManager struct {
    isPlatformFree bool
    lock           *sync.Mutex
    trainQueue     []train
}

func newStationManger() *stationManager {
    return &stationManager{
        isPlatformFree: true,
        lock:           &sync.Mutex{},
    }
}

func (s *stationManager) canLand(t train) bool {
    s.lock.Lock()
    defer s.lock.Unlock()
    if s.isPlatformFree {
        s.isPlatformFree = false
        return true
    }
    s.trainQueue = append(s.trainQueue, t)
    return false
}

func (s *stationManager) notifyFree() {
    s.lock.Lock()
    defer s.lock.Unlock()
    if !s.isPlatformFree {
        s.isPlatformFree = true
    }
    if len(s.trainQueue) > 0 {
        firstTrainInQueue := s.trainQueue[0]
        s.trainQueue = s.trainQueue[1:]
        firstTrainInQueue.permitArrival()
    }
}

main.go

package main

func main() {
    stationManager := newStationManger()
    passengerTrain := &passengerTrain{
        mediator: stationManager,
    }
    goodsTrain := &goodsTrain{
        mediator: stationManager,
    }
    passengerTrain.requestArrival()
    goodsTrain.requestArrival()
    passengerTrain.departure()
}

Output:

PassengerTrain: Landing
GoodsTrain: Waiting
PassengerTrain: Leaving
GoodsTrain: Arrival Permitted. Landing

Full Working Code: #

package main

import (
    "fmt"
    "sync"
)

type train interface {
    requestArrival()
    departure()
    permitArrival()
}

type passengerTrain struct {
    mediator mediator
}

func (g *passengerTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("PassengerTrain: Landing")
    } else {
        fmt.Println("PassengerTrain: Waiting")
    }
}

func (g *passengerTrain) departure() {
    fmt.Println("PassengerTrain: Leaving")
    g.mediator.notifyFree()
}

func (g *passengerTrain) permitArrival() {
    fmt.Println("PassengerTrain: Arrival Permitted. Landing")
}

type goodsTrain struct {
    mediator mediator
}

func (g *goodsTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("GoodsTrain: Landing")
    } else {
        fmt.Println("GoodsTrain: Waiting")
    }
}

func (g *goodsTrain) departure() {
    g.mediator.notifyFree()
    fmt.Println("GoodsTrain: Leaving")
}

func (g *goodsTrain) permitArrival() {
    fmt.Println("GoodsTrain: Arrival Permitted. Landing")
}

type mediator interface {
    canLand(train) bool
    notifyFree()
}

type stationManager struct {
    isPlatformFree bool
    lock           *sync.Mutex
    trainQueue     []train
}

func newStationManger() *stationManager {
    return &stationManager{
        isPlatformFree: true,
        lock:           &sync.Mutex{},
    }
}

func (s *stationManager) canLand(t train) bool {
    s.lock.Lock()
    defer s.lock.Unlock()
    if s.isPlatformFree {
        s.isPlatformFree = false
        return true
    }
    s.trainQueue = append(s.trainQueue, t)
    return false
}

func (s *stationManager) notifyFree() {
    s.lock.Lock()
    defer s.lock.Unlock()
    if !s.isPlatformFree {
        s.isPlatformFree = true
    }
    if len(s.trainQueue) > 0 {
        firstTrainInQueue := s.trainQueue[0]
        s.trainQueue = s.trainQueue[1:]
        firstTrainInQueue.permitArrival()
    }
}

func main() {
    stationManager := newStationManger()
    passengerTrain := &passengerTrain{
        mediator: stationManager,
    }
    goodsTrain := &goodsTrain{
        mediator: stationManager,
    }
    passengerTrain.requestArrival()
    goodsTrain.requestArrival()
    passengerTrain.departure()
}

Output:

PassengerTrain: Landing
GoodsTrain: Waiting
PassengerTrain: Leaving
GoodsTrain: Arrival Permitted. Landing