設計模式 in Go: Mediator
行爲模式旨在解決對象間通信和交互相關的問題,專注於定義那些複雜到無法靜態設計的協作策略,這些協作策略使得程序可以在運行時動態地進行職責派遣以實現更好的擴展。
今天我們開始第 7 個行爲模式的學習 —— Mediator(中介者模式)。
問題背景:
中介模式用於當一組對象需要以鬆散耦合的方式相互通信時。在複雜系統中,對象之間的直接通信會導致緊耦合和增加複雜性。中介模式有助於集中和控制對象間的通信,減少依賴關係,使系統更易於維護。
解決方案:
中介模式引入了一個封裝一組對象之間通信邏輯的中介對象。對象不再直接相互通信,而是通過中介進行通信。這降低了對象間的耦合,並使系統的修改和擴展更加容易。
中介充當中央樞紐,接收來自對象的消息並將其分發給適當的接收者。它瞭解各個對象及其職責,從而能夠協調它們的交互。這樣,對象無需直接瞭解彼此,減少了依賴關係,並使系統更加靈活。
Fitting Room Mediator 例如,假設你是一名試衣間中介,有許多顧客和有限的試衣間。你需要檢查是否有空閒的試衣間,並告知顧客可以進入或需要等待。如果某個試衣間被佔用超過五分鐘,你還需通知該顧客請儘快一些 :)
示例代碼:
package mediator
import (
"log"
"sync"
"sync/atomic"
"time"
)
// Mediator defines the fitting room mediator behavior
type Mediatorinterface {
// Wait let customer wait untils a fitting room released
Wait(*Customer) <-chan *FittingRoom
// Enter let customer enter a fitting room
Enter(*Customer)
// Leave let customer leave a fitting room
Leave(*Customer)
}
const (
idle =iota
reserved
inused
)
type FittingRoomstruct {
noint
stateint
customer *Customer
last time.Time
}
// a customer who waits for a fitting room
type waiterstruct {
*Customer
chchan *FittingRoom
}
// NewFittingRoomMediator create a mediator who manages `cap` fitting rooms
funcNewFittingRoomMediator(capint) *fittingRoomMediator {
rooms :=make([]*FittingRoom,cap)
for i :=0; i <cap; i++ {
rooms[i] = &FittingRoom{
no: i +1,
state: idle,
customer:nil,
}
}
m := &fittingRoomMediator{
Rooms: rooms,
WaitingCustomers:make(chan *waiter,64),
}
return m
}
type fittingRoomMediatorstruct {
sync.Mutex
Rooms []*FittingRoom
WaitingCustomerschan *waiter
}
func(m *fittingRoomMediator) Wait(customer *Customer) <-chan *FittingRoom {
m.Lock()
defer m.Unlock()
ch :=make(chan *FittingRoom,1)
for _, room :=range m.Rooms {
if room.state == idle ||
(room.state == reserved && time.Since(room.last) > time.Minute*5) {
room.state = reserved
room.customer = customer
room.last = time.Now()
ch <- room
return ch
}
}
m.WaitingCustomers <- &waiter{Customer: customer, ch: ch}
return ch
}
func(m *fittingRoomMediator) Enter(customer *Customer) {
m.Lock()
defer m.Unlock()
for _, room :=range m.Rooms {
if room.state != reserved {
continue
}
if room.customer.sequence == customer.sequence {
room.state = inused
room.last = time.Now()
return
}
}
log.Printf("%s no reserved room found\n", customer.Name)
}
func(m *fittingRoomMediator) Leave(customer *Customer) {
m.Lock()
defer m.Unlock()
for _, room :=range m.Rooms {
if room.state != inused {
continue
}
if room.customer.sequence == customer.sequence {
room.state = idle
room.customer =nil
room.last = time.Now()
log.Printf("%s will leave room-%d\n", customer.Name, room.no)
// let next waiting customer enter
select {
case waiter := <-m.WaitingCustomers:
log.Printf("%s will be notified to use room-%d\n", waiter.Name, room.no)
room.state = reserved
room.customer = waiter.Customer
room.last = time.Now()
waiter.ch <- room
close(waiter.ch)
default:
}
}
}
}
var sequence atomic.Int64
funcNewCustomer(namestring) *Customer {
return &Customer{
Name: name,
sequence:int(sequence.Add(1)),
}
}
type Customerstruct {
Namestring
sequenceint
mediator Mediator
}
func(c *Customer) SetMediator(m Mediator) *Customer {
c.mediator = m
return c
}
func(c *Customer) Enter() {
room, ok := <-c.mediator.Wait(c)
if !ok {
log.Printf("%s wait too long, it's best to quit\n", c.Name)
return
}
log.Printf("%s could use reserved room-%d\n", c.Name, room.no)
c.mediator.Enter(c)
log.Printf("%s now is using room-%d\n", c.Name, room.no)
}
func(c *Customer) Leave() {
c.mediator.Leave(c)
log.Printf("%s has left\n", c.Name)
}
運行以下測試以演示:
package mediator
import (
"testing"
"time"
)
funcTestMediator(t *testing.T) {
mediator := NewFittingRoomMediator(2)
c1 := NewCustomer("zhang").SetMediator(mediator)
c2 := NewCustomer("yang").SetMediator(mediator)
c3 := NewCustomer("wang").SetMediator(mediator)
ch :=make(chanint,2)
gofunc() {
c1.Enter()
c2.Enter()
c3.Enter()
ch <-1
}()
gofunc() {
time.Sleep(time.Second *5)
c1.Leave()
c2.Leave()
time.Sleep(time.Second *5)
c3.Leave()
ch <-1
}()
<-ch
<-ch
}
Running tool: /usr/local/go/bin/gotest -timeout 30s -run ^TestMediator$ mediator -v -count=1
=== RUN TestMediator
2024/11/14 14:34:10 zhang could use reserved room-1
2024/11/14 14:34:10 zhang now is using room-1
2024/11/14 14:34:10 yang could use reserved room-2
2024/11/14 14:34:10 yang now is using room-2
2024/11/14 14:34:15 zhang will leave room-1
2024/11/14 14:34:15 wang will be notified to use room-1
2024/11/14 14:34:15 zhang has left
2024/11/14 14:34:15 yang will leave room-2
2024/11/14 14:34:15 yang has left
2024/11/14 14:34:15 wang could use reserved room-1
2024/11/14 14:34:15 wang now is using room-1
2024/11/14 14:34:20 wang will leave room-1
2024/11/14 14:34:20 wang has left
--- PASS: TestMediator (10.01s)
PASS
ok mediator 10.009s
ps:此處我們優先考慮可讀性,不會太關注編碼標準,如註釋、camelCase 類型名等。我們將多個文件的代碼組織到一個 codeblock 中僅僅是爲了方便閱讀,如果您想測試可以通過 git 下載源碼 github.com/hitzhangjie/go-patterns。
中介模式的變體:
中介模式有幾種變體,可以根據系統的具體需求來使用:
- 事件驅動的中介:在這種變體中,中介通過事件系統來促進對象之間的通信。對象可以訂閱特定事件,並且當需要時,中介會觸發這些事件。這允許更解耦和異步的通信模型。
- 特定組對象的中介:在這種變體中,中介被定製爲處理某些組對象之間特定的交互。它可以爲不同組的對象提供不同的中介實現,從而可以更精細地控制通信。
- 帶有狀態的中介:這種變體擴展了基本的中介模式,引入了一種狀態管理機制。中介會跟蹤對象的狀態,並根據這些狀態調整通信流程。當對象的行爲依賴於某些條件或狀態時,這非常有用。
與其他模式關係:
中介模式可以與以下其他模式相關聯:
- Observer(觀察者模式):中介模式可以被視爲觀察者模式的擴展。雖然觀察者模式側重於一對多通信,但中介模式處理多對多通信。中介充當中央樞紐,協調多個對象之間的交互。
- Singleton(單例模式):在某些情況下,中介對象被實現爲單例,以確保只有一個實例來管理對象間的通信。當系統交互需要單一控制點時,這非常有用。
- Facade(外觀模式):中介模式可以與外觀模式結合使用,簡化複雜的系統。中介充當 Facade,提供一個簡化的接口供對象相互通信。這減少了對象之間的複雜性和依賴性。
本文總結:
中介模式提供了一種集中和控制系統中對象間通信的方式。它促進鬆散耦合,減少依賴關係,並提高系統的可維護性。可以根據系統的具體需求以不同的變體來實現中介模式。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/VlWo5umWKVzKsqhO4ZllFA