设计模式 观察者模式
基础概念
来自Head First设计模式一书的定义
观察者模式 Observer 观察者模式 定义了一系列对象之间的一对多的关系,当一个对象的状态改变, 其他依赖者都会收到到通知。
经常观察者模式也称发布订阅模式,一般观察者模式有以下组成:
- Subject-被观察者,亦或是发布者-Publisher
- Observer-观察者,亦或是订阅者-Subscribe
经典的实现方式
以下是golang的具体实现:observer.go
package design_mode
import (
"fmt"
)
type Subject interface {
RegisterObserver(Observer)
NotifyObservers(message interface{})
}
type Observer interface {
Update(message interface{})
}
type ConcreteSubject struct {
observerList []Observer
}
func NewConcreteSubject() Subject {
return &ConcreteSubject{}
}
func (o *ConcreteSubject) RegisterObserver(observer Observer) {
o.observerList = append(o.observerList, observer)
}
func (o *ConcreteSubject) NotifyObservers(message interface{}) {
for _, observer := range o.observerList {
observer.Update(message)
}
}
type ConcreteObserverOne struct {
}
func (b *ConcreteObserverOne) Update(message interface{}) {
fmt.Printf("ConcreteObserverOne is notified.%v \n", message)
}
type ConcreteObserverTwo struct {
}
func (b *ConcreteObserverTwo) Update(message interface{}) {
fmt.Printf("ConcreteObserverOne is notified.%v \n", message)
}
observer_test.go
package design_mode
import "testing"
func TestObserver(t *testing.T) {
concreteSubject := NewConcreteSubject()
concreteSubject.RegisterObserver(&ConcreteObserverOne{})
concreteSubject.RegisterObserver(&ConcreteObserverTwo{})
concreteSubject.NotifyObservers("hello every one")
}
OutPut:
ConcreteObserverOne is notified.hello every one
ConcreteObserverOne is notified.hello every one
可以看到例子里包含了以下接口和实现:
- Subject 主题接口: 注册实现、通知所有观察者
- Observer 观察者接口: 接收主体通知的接口。
- ConcreteSubject 主题的具体实现
- ConcreteObserverOne/ConcreteObserverTwo 不同的观察者实现
当你需要增加一个观察者时,只需要实现 Update()接口和注册Register到subject即可。
应用场景
那么观察者模式在什么场景下适用呢?接着我们以一个游戏用户注册的例子来套用一下。
场景: 玩家在注册成功后,将在“地图服务”创建一个出生点位,同时“邮件服务”会发送一份新手礼包。
在没有使用观察者模式时,可能是这么写的。
package main
import "fmt"
type WorldMapService struct {
}
func NewWorldMapService() WorldMapService {
return WorldMapService{}
}
func (w WorldMapService) Join(u User) {
fmt.Println(fmt.Sprintf("欢迎%v来到新手村", u.Name))
}
type MailService struct {
}
func NewMailService() MailService {
return MailService{}
}
func (w MailService) Send(u User) {
fmt.Println(fmt.Sprintf("恭喜勇士%v,获得金币999", u.Name))
}
type UserService struct {
}
func NewUserService() UserService {
return UserService{}
}
type User struct {
Name string
}
func (u UserService) Register(name string) User {
return User{Name: name}
}
func (m User) Start() {
fmt.Printf("欢迎来到元宇宙")
}
type UserController struct {
User UserService
WorldMap WorldMapService
Mail MailService
}
func NewUserController() UserController {
user := NewUserService()
worldMap := NewWorldMapService()
mail := NewMailService()
return UserController{
User: user,
WorldMap: worldMap,
Mail: mail,
}
}
func (c UserController) Do() {
user := c.User.Register("好奇的小明")
c.WorldMap.Join(user)
c.Mail.Send(user)
user.Start()
}
func main() {
controller := NewUserController()
controller.Do()
}
OutPut:
MapService: 欢迎好奇的小明来到新手村
MailService: 恭喜勇士好奇的小明,获得金币999
UserService: 让我们开始愉快的旅程吧
UserController.Do 注册、增加出生点位、发送邮件,违反单一职责原则。如果模块越来越多,比如增加一个任务系统(登入过游戏赠送金币),坐骑模块(登入赠送初始坐骑)之类,那么这里的代码就会变得越来越长,不好拓展等。这时候,使用观察者模式,进行解耦。
package main
import "fmt"
type WorldMapService struct {
}
func NewWorldMapService() WorldMapService {
return WorldMapService{}
}
func (w WorldMapService) HandleRegSuccess(u User) {
fmt.Println(fmt.Sprintf("MapService: 欢迎%v来到新手村", u.Name))
}
type MailService struct {
}
func NewMailService() *MailService {
return &MailService{}
}
func (w MailService) HandleRegSuccess(u User) {
fmt.Println(fmt.Sprintf("MailService: 恭喜勇士%v,获得金币999", u.Name))
}
type UserService struct {
}
func NewUserService() UserService {
return UserService{}
}
type User struct {
Name string
}
func (u UserService) Register(name string) User {
return User{Name: name}
}
func (m User) Start() {
fmt.Printf("UserService: 让我们开始愉快的旅程吧")
}
type UserController struct {
obsrvers []RegObserver
}
func NewUserController(obsrvers ...RegObserver) UserController {
return UserController{obsrvers: obsrvers}
}
func (c UserController) Do() {
userSvc := NewUserService()
user := userSvc.Register("好奇的小明")
for _, ob := range c.obsrvers {
ob.HandleRegSuccess(user)
}
user.Start()
}
type RegObserver interface {
HandleRegSuccess(User)
}
func main() {
controller := NewUserController(NewWorldMapService(), NewMailService())
controller.Do()
}
这里
-
定义了一个RegObserver接口,不同的服务都实现了这个接口。并注册到了userController,控制器保存了所有的观察者,在登入userController的执行函数Do里,会去遍历所有的观察者,执行HandleRegSuccess。
-
当拓展需求时,只需要再添加一个实现了RegObserver接口的类并注册到控制器即可。
如此,各模块间耦合性就降低了。
小结
本文主要介绍观察者模式和使用场景,观察者模式和发布订阅模式的思路是差不多的,主要是为了解耦。应用场景还是非常广泛的,在同一进程的编码上, 在进程间的消息队列,还是在产品的订阅模式都有异曲同工之处。