Skip to content

Commit 746535b

Browse files
committed
docs(golang): 更新设计模式笔记。
更新观察者模式笔记。 更新状态模式笔记。
1 parent d70ac8f commit 746535b

File tree

4 files changed

+540
-154
lines changed

4 files changed

+540
-154
lines changed
48.3 KB
Loading

docs/zh/后端/01-Golang/03-Golang设计模式/20-观察者模式.md

Lines changed: 136 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -35,152 +35,142 @@ title: 观察者模式
3535
classDiagram
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
10086
package 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
144119
package 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
192182
package 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

Comments
 (0)