@@ -35,152 +35,142 @@ title: 观察者模式
3535classDiagram
3636 direction LR
3737
38- %% 主题接口(Subject):Subject
38+ %% 定义接口
3939 class Subject {
4040 <<interface>>
41- +RegisterObserver(o Observer)
42- +RemoveObserver(o Observer)
43- +NotifyObservers( )
41+ +Attach(observer Observer)
42+ +Detach(observer Observer)
43+ +Notify(message string )
4444 }
45- note for Subject "定义主题的接口,管理观察者"
4645
47- %% 具体主题(Concrete Subject):NewsAgency
48- class NewsAgency {
49- -observers []Observer
50- -news string
51- +RegisterObserver(o Observer)
52- +RemoveObserver(o Observer)
53- +NotifyObservers()
54- +SetNews(news string)
55- }
56-
57- %% 观察者接口(Observer):Observer
5846 class Observer {
5947 <<interface>>
60- +Update(news string)
48+ +Update(message string)
6149 }
62- note for Observer "定义观察者的接口,处理通知"
6350
64- %% 具体观察者(Concrete Observer):NewsChannel
65- class NewsChannel {
66- -name string
67- -news string
68- +Update(news string)
69- +GetNews() string
51+ %% 定义具体类
52+ class ConcreteSubject {
53+ -observers []Observer
54+ -state string
55+ +Attach(observer Observer)
56+ +Detach(observer Observer)
57+ +Notify(message string)
58+ +GetState() string
7059 }
7160
72- %% 定义关系
73- Subject <|.. NewsAgency : 实现
74- Observer <|.. NewsChannel : 实现
75- NewsAgency o--> "many" Observer : 管理
76- ```
77-
78- ### 新闻发布示例
61+ class ConcreteObserver {
62+ -name string
63+ +Update(message string)
64+ }
7965
80- ` subject.go ` 代码如下:
66+ class Client {
8167
82- ``` go
83- package observer
84-
85- // 观察者模式
68+ }
8669
87- // 主题接口
70+ %% 关系描述
71+ Subject <|.. ConcreteSubject : 实现
72+ Observer <|.. ConcreteObserver : 实现
73+ Subject o--> Observer : 聚合 (持有列表)
74+ ConcreteSubject ..> Observer : 依赖 (调用 Update)
8875
89- // Subject 定义主题接口,管理观察者
90- type Subject interface {
91- RegisterObserver (o Observer )
92- RemoveObserver (o Observer )
93- NotifyObservers ()
94- }
76+ Client ..> ConcreteSubject : 1. 创建 & 触发变更
77+ Client ..> ConcreteObserver : 2. 创建实例
78+ Client ..> Subject : 3. 调用 Attach/Detach
9579```
9680
97- ` news_agency.go ` 代码如下:
81+ ### 新闻发布示例
82+
83+ ` observer.go ` 代码如下:
9884
9985``` go
10086package observer
10187
102- // 观察者模式
103-
104- // 具体主题
88+ import " fmt"
10589
106- // NewsAgency 是具体主题,管理新闻和观察者
107- type NewsAgency struct {
108- observers []Observer
109- news string
110- }
90+ // 观察者模式(观察者接口与具体实现)
11191
112- // RegisterObserver 添加观察者
113- func (n *NewsAgency ) RegisterObserver (o Observer ) {
114- n.observers = append (n.observers , o)
92+ // Observer 定义观察者接口
93+ // 所有具体的观察者都必须实现这个方法,当主题状态改变时会被调用
94+ type Observer interface {
95+ // Update 当主题状态发生变化时,主题会调用此方法通知观察者
96+ // 参数 message 是主题传递过来的最新状态信息
97+ Update (message string )
11598}
11699
117- // RemoveObserver 移除观察者
118- func (n *NewsAgency ) RemoveObserver (o Observer ) {
119- for i , observer := range n.observers {
120- if observer == o {
121- n.observers = append (n.observers [:i], n.observers [i+1 :]...)
122- break
123- }
124- }
100+ // ConcreteObserver 具体观察者实现
101+ type ConcreteObserver struct {
102+ name string // 观察者名称,用于区分不同的观察者
125103}
126104
127- // NotifyObservers 通知所有观察者
128- func (n *NewsAgency ) NotifyObservers () {
129- for _ , observer := range n.observers {
130- observer.Update (n.news )
131- }
105+ // NewConcreteObserver 创建一个具体的观察者实例
106+ func NewConcreteObserver (name string ) *ConcreteObserver {
107+ return &ConcreteObserver{name: name}
132108}
133109
134- // SetNews 设置新闻并通知观察者
135- func (n *NewsAgency ) SetNews (news string ) {
136- n.news = news
137- n.NotifyObservers ()
110+ // Update 实现 Observer 接口,接收主题的通知并打印
111+ func (o *ConcreteObserver ) Update (message string ) {
112+ fmt.Printf (" [观察者: %s ] 收到状态更新通知 → %s \n " , o.name , message)
138113}
139114```
140115
141- ` observer .go` 代码如下:
116+ ` subject .go` 代码如下:
142117
143118``` go
144119package observer
145120
146- // 观察者模式
147-
148- // 观察者接口
121+ // 观察者模式(主题接口与具体实现)
149122
150- // Observer 定义观察者接口,处理通知
151- type Observer interface {
152- Update (news string )
153- }
154- ```
123+ // Subject 定义主题(被观察者)接口
124+ type Subject interface {
125+ // Attach 注册/添加一个观察者
126+ Attach (observer Observer )
155127
156- ` news_channel.go ` 代码如下:
128+ // Detach 注销/移除一个观察者
129+ Detach (observer Observer )
157130
158- ``` go
159- package observer
131+ // Notify 通知所有已注册的观察者状态已变更
132+ Notify (message string )
133+ }
160134
161- // 观察者模式
135+ // ConcreteSubject 具体的主题实现(被观察的对象)
136+ type ConcreteSubject struct {
137+ observers []Observer // 保存所有注册的观察者
138+ state string // 主题当前的状态(可选,用于业务扩展)
139+ }
162140
163- // 具体观察者
141+ // NewConcreteSubject 创建一个具体的主题实例
142+ func NewConcreteSubject () *ConcreteSubject {
143+ return &ConcreteSubject{
144+ observers: make ([]Observer, 0 ),
145+ }
146+ }
164147
165- // NewsChannel 是具体观察者,接收和存储新闻
166- type NewsChannel struct {
167- name string
168- news string
148+ // Attach 添加观察者到列表中
149+ func (s *ConcreteSubject ) Attach (observer Observer ) {
150+ s.observers = append (s.observers , observer)
169151}
170152
171- // NewNewsChannel 创建具体观察者实例
172- func NewNewsChannel (name string ) *NewsChannel {
173- return &NewsChannel{name: name}
153+ // Detach 从列表中移除指定的观察者
154+ func (s *ConcreteSubject ) Detach (observer Observer ) {
155+ for i , obs := range s.observers {
156+ if obs == observer { // 引用相同即认为相等
157+ s.observers = append (s.observers [:i], s.observers [i+1 :]...)
158+ return
159+ }
160+ }
174161}
175162
176- // Update 更新观察者的新闻内容
177- func (n *NewsChannel ) Update (news string ) {
178- n.news = news
163+ // Notify 遍历所有观察者,调用它们的 Update 方法进行通知
164+ func (s *ConcreteSubject ) Notify (message string ) {
165+ s.state = message // 更新主题状态(实际项目中可能有更多字段)
166+ for _ , observer := range s.observers {
167+ observer.Update (message)
168+ }
179169}
180170
181- // GetNews 获取观察者的新闻内容
182- func (n * NewsChannel ) GetNews () string {
183- return n. news
171+ // GetState (可选)获取当前状态,用于观察者主动拉取(拉模型扩展)
172+ func (s * ConcreteSubject ) GetState () string {
173+ return s. state
184174}
185175```
186176
@@ -191,71 +181,64 @@ func (n *NewsChannel) GetNews() string {
191181``` go
192182package observer
193183
194- import (
195- " testing"
196- )
184+ import " testing"
197185
198186// 单元测试
199187// 模拟客户端调用
200188
201- // TestObserver 测试观察者模式的场景
202- func TestObserver (t *testing .T ) {
203- agency := &NewsAgency{}
204- channel1 := NewNewsChannel (" 频道1" )
205- channel2 := NewNewsChannel (" 频道2" )
206-
207- tests := []struct {
208- name string
209- news string
210- expectedNews string
211- }{
212- {
213- name: " 发布第一条新闻" ,
214- news: " 突发新闻:事件A" ,
215- expectedNews: " 突发新闻:事件A" ,
216- },
217- {
218- name: " 发布第二条新闻" ,
219- news: " 最新报道:事件B" ,
220- expectedNews: " 最新报道:事件B" ,
221- },
222- }
189+ // TestObserverPattern 模拟真实客户端使用观察者模式的完整流程
190+ func TestObserverPattern (t *testing .T ) {
191+ // 1. 创建被观察者(主题)
192+ newsAgency := NewConcreteSubject ()
223193
224- // 注册观察者
225- agency.RegisterObserver (channel1)
226- agency.RegisterObserver (channel2)
227-
228- for _ , tt := range tests {
229- t.Run (tt.name , func (t *testing.T ) {
230- // 发布新闻
231- agency.SetNews (tt.news )
232-
233- // 验证观察者接收的新闻
234- if result := channel1.GetNews (); result != tt.expectedNews {
235- t.Errorf (" 频道1 期望新闻 %q ,实际得到 %q " , tt.expectedNews , result)
236- }
237- if result := channel2.GetNews (); result != tt.expectedNews {
238- t.Errorf (" 频道2 期望新闻 %q ,实际得到 %q " , tt.expectedNews , result)
239- }
240- t.Logf (" 新闻发布: %s " , tt.expectedNews )
241- })
242- }
194+ // 2. 创建多个具体观察者(比如不同的新闻订阅者)
195+ tvStation := NewConcreteObserver (" 电视台" )
196+ wechatUser := NewConcreteObserver (" 微信用户" )
197+ appUser := NewConcreteObserver (" 手机APP用户" )
243198
244- // 测试移除观察者
245- agency.RemoveObserver (channel2)
246- agency.SetNews (" 新事件:事件C" )
247- if channel2.GetNews () != " 最新报道:事件B" {
248- t.Errorf (" 频道2 不应接收新新闻,期望 %q ,实际得到 %q " , " 最新报道:事件B" , channel2.GetNews ())
249- }
250- if channel1.GetNews () != " 新事件:事件C" {
251- t.Errorf (" 频道1 期望新闻 %q ,实际得到 %q " , " 新事件:事件C" , channel1.GetNews ())
252- }
199+ // 3. 订阅(注册观察者)
200+ newsAgency.Attach (tvStation)
201+ newsAgency.Attach (wechatUser)
202+ newsAgency.Attach (appUser)
203+
204+ t.Log (" 第一次发布新闻,所有人都能收到..." )
205+ // 4. 主题状态改变 → 自动通知所有观察者(推模型)
206+ newsAgency.Notify (" 紧急:最新天气预报发布!" )
207+
208+ t.Log (" \n 微信用户取关了..." )
209+ // 5. 某个观察者取消订阅
210+ newsAgency.Detach (wechatUser)
211+
212+ t.Log (" 第二次发布新闻,只有剩余订阅者收到..." )
213+ newsAgency.Notify (" 股市大涨,指数突破14000点!" )
253214}
254215```
255216
256217### 实现说明
257218
258- 观察者模式通过 ` Subject ` 接口定义了主题的行为,` NewsAgency ` (具体主题)维护观察者列表并在新闻更新时通知所有观察者。` Observer ` 接口定义了观察者的更新方法,` NewsChannel ` (具体观察者)接收并存储新闻内容。客户端通过注册和移除观察者、设置新闻内容来触发通知。测试代码验证了观察者模式的动态注册、通知和移除功能,展示了主题与观察者的解耦以及状态更新的正确性。
219+ ** 接口解耦:**
220+
221+ - 通过 ` Subject ` 和 ` Observer ` 接口定义交互契约,实现了发布者与订阅者的松耦合。` Subject ` 只知道观察者实现了 ` Update ` 方法,而不关心具体的业务逻辑。
222+ - ` Observer ` 接口仅包含 ` Update(message string) ` 方法,这是一种推模型(` Push Model ` )的实现,主题在通知时直接将数据(` message ` )推送给观察者。
223+
224+ ** 主题(Subject)管理机制:**
225+
226+ - ` ConcreteSubject ` 使用切片 ` []Observer ` 来存储所有活跃的观察者列表。
227+ - 注册(Attach):使用 Go 的内建函数 ` append ` 将新的观察者加入切片。
228+ - 注销(Detach):遍历切片,通过对比接口对象的引用地址找到目标观察者,并利用切片操作 ` append(s.observers[:i], s.observers[i+1:]...) ` 将其移除。这是 Go 语言中删除切片元素的标准做法。
229+ - 通知(Notify):遍历 ` observers ` 切片,逐一调用观察者的 ` Update ` 方法。这里是同步调用,意味着如果某个观察者处理耗时较长,会阻塞后续观察者的通知。
230+
231+ ** 观察者(Observer)响应:**
232+
233+ - ` ConcreteObserver ` 内部维护了 ` name ` 字段用于区分身份。
234+ - 收到 ` Update ` 调用时,直接打印消息,模拟业务响应。
235+
236+ ** 客户端流程:**
237+
238+ - 客户端首先创建 ` ConcreteSubject ` ,然后实例化多个 ` ConcreteObserver ` 。
239+ - 通过 ` Attach ` 方法建立订阅关系。
240+ - 当调用 ` Notify ` 触发事件时,所有在列表中的观察者都会收到通知。
241+ - 演示了 ` Detach ` 后,被移除的观察者不再收到后续通知的逻辑。
259242
260243## 优点与缺点
261244
0 commit comments