

新闻资讯
技术学院Go中实现中介者模式的核心是用接口+组合控制依赖关系:User只持Mediator接口,不直接引用其他User;中介者统一处理转发逻辑,便于扩展审计、限流等功能,避免强耦合与重复代码。
Go 里实现中介者模式,核心不是“写个 ChatRoom 就完事”,而是用接口 + 组合把“谁该知道谁”这件事控制住——用户不持有其他用户,只持有一个 Mediator 接口;中介者持有所有用户指针,但用户之间完全无引用。
Receive?直接调用意味着强耦合:新增一个用户类型(比如 BotUser)就得改所有发送逻辑;想加消息审计、限流、日志,得在每个 SendTo 里重复写。中介者模式把“转发决策”收归一处,后续加广播策略、私聊校验、离线缓存,都只动 ChatRoom.Send,不动任何 User。
User.Send 里硬编码遍历 users 列表 —— 这等于绕过中介者,模式失效User 只认 Mediator 接口,具体怎么发、发给谁,由实现该接口的 *SimpleChatRoom 决定Send(from, to, msg string),别塞 BanUser 或 GetOnlineCount —— 那是业务逻辑,不是中介职责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 的不是并发,而是 User.Send 时 u.chatRoom 为 nil —— 因为忘了调 room.Register(u),或者注册发生在 User 初始化之后但 Send 调用之前。
立即学习“go语言免费学习笔记(深入)”;
User.Send 开头加 if u.chatRoom == nil { log.Warn("user not registered"); return }
NewChatUser(name, room),而不是先 NewChatUser(name) 再手动 SetChatRoom
map[string]User 不能裸用?Register 和 Send 很可能被不同 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:防止用户回调阻塞整个房间;应先收集目标用户切片,再解锁后遍历调用chan Message),注意缓冲区大小 —— 满了会 block Send,影响实时性中介者模式真正的复杂点不在代码行数,而在“边界感”:哪些逻辑必须塞进中介者(比如消息格式校验、用户状态检查),哪些必须留在用户侧(比如 UI 渲染、本地缓存)。一旦中介者开始处理渲染逻辑或调用数据库,它就不再是协调者,而是上帝对象 —— 后续谁都改不动。