GO设计模式——21、观察者模式(行为型)

目录

[观察者模式(Observer Pattern)](#观察者模式(Observer Pattern))

观察者模式的核心角色:

优缺点

使用场景

注意事项

代码实现


观察者模式(Observer Pattern)

观察者模式(Observer Pattern)定义了对象间的一种一对多的依赖关系,使得当一个对象状态发生改变时,所有依赖于它的对象都可以自动得到通知并且被更新。观察者模式是一种一对多的关系,可以有任意个(1个或多个)观察者对象同时监听(观察)某一个对象。

观察者模式核心角色

  • 主题(Subject):负责维护一组观察者,并在状态改变时通知观察者。也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
  • 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
  • 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
  • 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。

优缺点

(1)优点:

  • 观察者和被观察者是抽象耦合的。
  • 建立一套触发机制。

(2)缺点:

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象......,可以使用观察者模式创建一种链式触发机制。

注意事项

  • 避免循环引用。
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

代码实现

Go 复制代码
package main

import "fmt"

// 新闻发布系统,用户可以订阅感兴趣的新闻主题,并在新闻发布时收到通知。
// 希望能够实现一个观察者模式,使订阅者能够接收到相关新闻的通知。

// 主题接口
type Subject interface {
    Register(observer Observer)
    Unregister(observer Observer)
    Notify()
}

// 具体主题
type NewsSubject struct {
    observers []Observer
    news      string
}

// 注册
func (n *NewsSubject) Register(observer Observer) {
    n.observers = append(n.observers, observer)
}

// 注销
func (n *NewsSubject) Unregister(observer Observer) {
    for i, o := range n.observers {
       if o == observer {
          n.observers = append(n.observers[:i], n.observers[i+1:]...)
          break
       }
    }
}

// 通知观察者
func (n *NewsSubject) Notify() {
    for _, observer := range n.observers {
       observer.Update(n)
    }
}

func (n *NewsSubject) SetNews(news string) {
    n.news = news
    n.Notify()
}

// 观察者接口
type Observer interface {
    Update(subject Subject)
}

// 具体观察者
type Subscriber struct {
    name string
}

func (s *Subscriber) Update(subject Subject) {
    newsSubject := subject.(*NewsSubject)
    fmt.Printf("[%s] 收到新闻通知:%s\n", s.name, newsSubject.news)
}

// 客户端代码
func main() {
    subject := &NewsSubject{}

    subscriber1 := &Subscriber{name: "订阅者1"}
    subject.Register(subscriber1)

    subscriber2 := &Subscriber{name: "订阅者2"}
    subject.Register(subscriber2)

    subject.SetNews("新闻1发布了")
    subject.SetNews("新闻2发布了")

    subject.Unregister(subscriber1)

    subject.SetNews("新闻3发布了")
}
相关推荐
程序员爱钓鱼5 小时前
Go语言实战案例——进阶与部署篇:编写Makefile自动构建Go项目
后端·算法·go
该用户已不存在6 小时前
别再用 if err != nil 了,学会这几个技巧,假装自己是Go大神
后端·go
李广坤7 小时前
状态模式(State Pattern)
设计模式
n8n8 小时前
Go语言操作Redis全面指南
go
李广坤8 小时前
观察者模式(Observer Pattern)
设计模式
李广坤9 小时前
中介者模式(Mediator Pattern)
设计模式
李广坤9 小时前
迭代器模式(Iterator Pattern)
设计模式
李广坤10 小时前
解释器模式(Interpreter Pattern)
设计模式
会跑的葫芦怪10 小时前
Go语言操作Redis
开发语言·redis·golang
静谧之心12 小时前
在 K8s 上可靠运行 PD 分离推理:RBG 的设计与实现
云原生·容器·golang·kubernetes·开源·pd分离