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

1package main
2
3type train interface {
4    requestArrival()
5    departure()
6    permitArrival()
7}

passengerTrain.go

 1package main
 2
 3import "fmt"
 4
 5type passengerTrain struct {
 6    mediator mediator
 7}
 8
 9func (g *passengerTrain) requestArrival() {
10    if g.mediator.canLand(g) {
11        fmt.Println("PassengerTrain: Landing")
12    } else {
13        fmt.Println("PassengerTrain: Waiting")
14    }
15}
16
17func (g *passengerTrain) departure() {
18    fmt.Println("PassengerTrain: Leaving")
19    g.mediator.notifyFree()
20}
21
22func (g *passengerTrain) permitArrival() {
23    fmt.Println("PassengerTrain: Arrival Permitted. Landing")
24}

goodsTrain.go

 1package main
 2
 3import "fmt"
 4
 5type goodsTrain struct {
 6    mediator mediator
 7}
 8
 9func (g *goodsTrain) requestArrival() {
10    if g.mediator.canLand(g) {
11        fmt.Println("GoodsTrain: Landing")
12    } else {
13        fmt.Println("GoodsTrain: Waiting")
14    }
15}
16
17func (g *goodsTrain) departure() {
18    g.mediator.notifyFree()
19    fmt.Println("GoodsTrain: Leaving")
20}
21
22func (g *goodsTrain) permitArrival() {
23    fmt.Println("GoodsTrain: Arrival Permitted. Landing")
24}

mediator.go

1package main
2
3type mediator interface {
4    canLand(train) bool
5    notifyFree()
6}

stationManager.go

 1package main
 2
 3import "sync"
 4
 5type stationManager struct {
 6    isPlatformFree bool
 7    lock           *sync.Mutex
 8    trainQueue     []train
 9}
10
11func newStationManger() *stationManager {
12    return &stationManager{
13        isPlatformFree: true,
14        lock:           &sync.Mutex{},
15    }
16}
17
18func (s *stationManager) canLand(t train) bool {
19    s.lock.Lock()
20    defer s.lock.Unlock()
21    if s.isPlatformFree {
22        s.isPlatformFree = false
23        return true
24    }
25    s.trainQueue = append(s.trainQueue, t)
26    return false
27}
28
29func (s *stationManager) notifyFree() {
30    s.lock.Lock()
31    defer s.lock.Unlock()
32    if !s.isPlatformFree {
33        s.isPlatformFree = true
34    }
35    if len(s.trainQueue) > 0 {
36        firstTrainInQueue := s.trainQueue[0]
37        s.trainQueue = s.trainQueue[1:]
38        firstTrainInQueue.permitArrival()
39    }
40}

main.go

 1package main
 2
 3func main() {
 4    stationManager := newStationManger()
 5    passengerTrain := &passengerTrain{
 6        mediator: stationManager,
 7    }
 8    goodsTrain := &goodsTrain{
 9        mediator: stationManager,
10    }
11    passengerTrain.requestArrival()
12    goodsTrain.requestArrival()
13    passengerTrain.departure()
14}

Output:

1PassengerTrain: Landing
2GoodsTrain: Waiting
3PassengerTrain: Leaving
4GoodsTrain: Arrival Permitted. Landing

Full Working Code: #

  1package main
  2
  3import (
  4    "fmt"
  5    "sync"
  6)
  7
  8type train interface {
  9    requestArrival()
 10    departure()
 11    permitArrival()
 12}
 13
 14type passengerTrain struct {
 15    mediator mediator
 16}
 17
 18func (g *passengerTrain) requestArrival() {
 19    if g.mediator.canLand(g) {
 20        fmt.Println("PassengerTrain: Landing")
 21    } else {
 22        fmt.Println("PassengerTrain: Waiting")
 23    }
 24}
 25
 26func (g *passengerTrain) departure() {
 27    fmt.Println("PassengerTrain: Leaving")
 28    g.mediator.notifyFree()
 29}
 30
 31func (g *passengerTrain) permitArrival() {
 32    fmt.Println("PassengerTrain: Arrival Permitted. Landing")
 33}
 34
 35type goodsTrain struct {
 36    mediator mediator
 37}
 38
 39func (g *goodsTrain) requestArrival() {
 40    if g.mediator.canLand(g) {
 41        fmt.Println("GoodsTrain: Landing")
 42    } else {
 43        fmt.Println("GoodsTrain: Waiting")
 44    }
 45}
 46
 47func (g *goodsTrain) departure() {
 48    g.mediator.notifyFree()
 49    fmt.Println("GoodsTrain: Leaving")
 50}
 51
 52func (g *goodsTrain) permitArrival() {
 53    fmt.Println("GoodsTrain: Arrival Permitted. Landing")
 54}
 55
 56type mediator interface {
 57    canLand(train) bool
 58    notifyFree()
 59}
 60
 61type stationManager struct {
 62    isPlatformFree bool
 63    lock           *sync.Mutex
 64    trainQueue     []train
 65}
 66
 67func newStationManger() *stationManager {
 68    return &stationManager{
 69        isPlatformFree: true,
 70        lock:           &sync.Mutex{},
 71    }
 72}
 73
 74func (s *stationManager) canLand(t train) bool {
 75    s.lock.Lock()
 76    defer s.lock.Unlock()
 77    if s.isPlatformFree {
 78        s.isPlatformFree = false
 79        return true
 80    }
 81    s.trainQueue = append(s.trainQueue, t)
 82    return false
 83}
 84
 85func (s *stationManager) notifyFree() {
 86    s.lock.Lock()
 87    defer s.lock.Unlock()
 88    if !s.isPlatformFree {
 89        s.isPlatformFree = true
 90    }
 91    if len(s.trainQueue) > 0 {
 92        firstTrainInQueue := s.trainQueue[0]
 93        s.trainQueue = s.trainQueue[1:]
 94        firstTrainInQueue.permitArrival()
 95    }
 96}
 97
 98func main() {
 99    stationManager := newStationManger()
100    passengerTrain := &passengerTrain{
101        mediator: stationManager,
102    }
103    goodsTrain := &goodsTrain{
104        mediator: stationManager,
105    }
106    passengerTrain.requestArrival()
107    goodsTrain.requestArrival()
108    passengerTrain.departure()
109}

Output:

1PassengerTrain: Landing
2GoodsTrain: Waiting
3PassengerTrain: Leaving
4GoodsTrain: Arrival Permitted. Landing