如何使用Golang实现中介者模式对象协作_Golang中介者模式消息分发示例

Go中实现中介者模式的核心是用接口+组合控制依赖关系:User只持Mediator接口,不直接引用其他User;中介者统一处理转发逻辑,便于扩展审计、限流等功能,避免强耦合与重复代码。

Go 里实现中介者模式,核心不是“写个 ChatRoom 就完事”,而是用接口 + 组合把“谁该知道谁”这件事控制住——用户不持有其他用户,只持有一个 Mediator 接口;中介者持有所有用户指针,但用户之间完全无引用。

为什么不能让 User 直接调用另一个 User 的 Receive

直接调用意味着强耦合:新增一个用户类型(比如 BotUser)就得改所有发送逻辑;想加消息审计、限流、日志,得在每个 SendTo 里重复写。中介者模式把“转发决策”收归一处,后续加广播策略、私聊校验、离线缓存,都只动 ChatRoom.Send,不动任何 User

  • 常见错误:在 User.Send 里硬编码遍历 users 列表 —— 这等于绕过中介者,模式失效
  • 正确做法:User 只认 Mediator 接口,具体怎么发、发给谁,由实现该接口的 *SimpleChatRoom 决定
  • 接口定义要窄:只暴露 Send(from, to, msg string),别塞 BanUserGetOnlineCount —— 那是业务逻辑,不是中介职责

Mediator 接口设计的关键取舍

Go 没有泛型约束(老版本)或泛型太重时,Mediator 接口参数用 string 还是 interface{}?实际项目中推荐字符串路由,轻量且易调试。

type Mediator interface {
    Send(from, to, message string)
}

// 而非
// Send(event Event) —— Event 需定义结构体、序列化、反序列化,小项目纯属累赘
// Notify(sender interface{}, data interface{}) —— 类型断言满天飞,测试难覆盖
  • 私聊场景:to 是用户名(string),查 map[string]User 即可,快且直观
  • 广播场景:to == ""to == "all",统一走 Broadcast 分支
  • 避免用 interface{} 当万能参数:一旦中介者内部要做 if v, ok := data.(UserAction); ok,就退化成 C 风格 void*,失去 Go 的类型安全优势

注册时机与空指针 panic 的真实来源

最常触发 panic 的不是并发,而是 User.Sendu.chatRoomnil —— 因为忘了调 room.Register(u),或者注册发生在 User 初始化之后但 Send 调用之前。

立即学习“go语言免费学习笔记(深入)”;

  • 防御写法:在 User.Send 开头加 if u.chatRoom == nil { log.Warn("user not registered"); return }
  • 更稳妥:用构造函数强制绑定,例如 NewChatUser(name, room),而不是先 NewChatUser(name) 再手动 SetChatRoom
  • 不要依赖延迟注册:比如在 HTTP handler 里才注册用户,而 goroutine 已经开始发心跳 —— 竞态+panic 双杀

并发安全:为什么 map[string]User 不能裸用?

RegisterSend 很可能被不同 goroutine 调用(如 WebSocket 连接建立 vs 消息接收),map 非并发安全,会直接 panic。

type SimpleChatRoom struct {
    mu    sync.RWMutex
    users map[string]User
}

func (c *SimpleChatRoom) Register(user User) {
    c.mu.Lock()
    defer c.mu.Unlock()
    user.SetChatRoom(c)
    c.users[user.GetName()] = user
    c.Broadcast("system", fmt.Sprintf("%s 加入了聊天室", user.GetName()))
}

func (c *SimpleChatRoom) Send(from, to, message string) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    // ... 查 map、转发逻辑
}
  • 别用 sync.Map 替代:它适合读多写少,但注册/下线是写密集操作,sync.RWMutex + map 更可控
  • 广播时别在锁内调 user.Receive:防止用户回调阻塞整个房间;应先收集目标用户切片,再解锁后遍历调用
  • 如果用 channel 做消息队列(如 chan Message),注意缓冲区大小 —— 满了会 block Send,影响实时性

中介者模式真正的复杂点不在代码行数,而在“边界感”:哪些逻辑必须塞进中介者(比如消息格式校验、用户状态检查),哪些必须留在用户侧(比如 UI 渲染、本地缓存)。一旦中介者开始处理渲染逻辑或调用数据库,它就不再是协调者,而是上帝对象 —— 后续谁都改不动。