基础概念

来自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()
}

这里

  1. 定义了一个RegObserver接口,不同的服务都实现了这个接口。并注册到了userController,控制器保存了所有的观察者,在登入userController的执行函数Do里,会去遍历所有的观察者,执行HandleRegSuccess。

  2. 当拓展需求时,只需要再添加一个实现了RegObserver接口的类并注册到控制器即可。

如此,各模块间耦合性就降低了。

小结

本文主要介绍观察者模式和使用场景,观察者模式和发布订阅模式的思路是差不多的,主要是为了解耦。应用场景还是非常广泛的,在同一进程的编码上, 在进程间的消息队列,还是在产品的订阅模式都有异曲同工之处。

参考