当前位置: 首页 > news >正文

Go语言设计模式:备忘录模式详解

文章目录

    • 一、备忘录模式概述
      • 1.1 备忘录模式介绍
      • 1.2 模式核心概念
      • 1.3 UML 类图
      • 1.4 备忘录模式优缺点分析
    • 二、Go语言实现备忘录模式
      • 2.1 步骤 1: 定义备忘录
      • 2.2 步骤 2: 实现发起人
      • 2.3 步骤 3: 实现负责人
      • 2.4 步骤 4: 客户端代码与演示
      • 2.5 Go语言中的变体:使用序列化
      • 2.6 完整代码
      • 2.7 执行结果和分析

一、备忘录模式概述

1.1 备忘录模式介绍

备忘录模式是一种行为设计模式,它允许在不破坏对象封装性的前提下,捕获并保存一个对象的内部状态,以便在将来可以将该对象恢复到这个保存的状态。

想象一下游戏中的存档功能。你可以在任何时候保存游戏(创建备忘录),如果之后操作失误,可以读取之前的存档(恢复状态),而不需要知道游戏内部是如何保存和恢复所有角色、场景数据的。

1.2 模式核心概念

备忘录模式主要包含三个核心角色:

  1. Originator (发起人)
    • 需要被保存状态的对象。
    • 它创建一个包含其当前内部状态的备忘录对象。
    • 它也可以使用备忘录对象来恢复其内部状态。
  2. Memento (备忘录)
    • 用于存储发起人的内部状态。
    • 备忘录的设计通常是其状态对外部不可见,只有发起人可以访问其内部信息。这保证了封装性。
  3. Caretaker (负责人/管理者)
    • 负责保存备忘录对象。
    • 它从不检查或操作备忘录的内容,它只是像一个“保险箱”一样,负责存储和提供备忘录。

1.3 UML 类图

"创建"
"恢复"
"保存"
Originator
-state: string
+SetState(state string)
+CreateMemento() : Memento
+Restore(memento Memento)
Memento
-state: string
-Memento(state string)
+GetState() : string
Caretaker
-mementos: []Memento
+Backup(memento Memento)
+Undo() : Memento

1.4 备忘录模式优缺点分析

优点

  1. 封装性得到保护:备忘录将发起人的内部状态封装起来,负责人无法看到或修改它,只有发起人自己能访问。
  2. 简化了发起人:发起人不需要自己管理历史版本,它只负责创建和恢复备忘录,将管理的职责交给了负责人。
  3. 提供了状态回滚的机制:可以轻松实现撤销、恢复、快照等功能。

缺点

  1. 资源消耗:如果发起人的状态很大,或者需要保存的历史版本很多,备忘录会占用大量内存。
  2. 维护成本:负责人需要存储所有的备忘录,如果备忘录对象本身很复杂,维护成本会很高。
  3. 实现可能很复杂:对于某些语言,保证备忘录的不可变性或封装性需要额外技巧。在Go中,通过包内可见性可以很好地解决。

二、Go语言实现备忘录模式

在Go语言中,我们可以通过结构体和接口的组合来实现备忘录模式。一个关键点是如何实现备忘录的“不可见性”。Go没有 private 构造函数,但我们可以通过将备忘录结构体定义在同一个包中,并将其字段设为小写(未导出),来限制外部包的访问。

我们通过一个文本编辑器的例子来演示:用户可以输入文本,随时保存快照,并能撤销到任何一个历史版本。

2.1 步骤 1: 定义备忘录

备忘录通常是一个简单的数据容器,用于存储状态。

// memento.go
package main
// TextEditorMemento 文本编辑器的备忘录
// 注意:state字段是小写的,意味着它只能在当前包内被访问,从而保护了封装性。
type TextEditorMemento struct {state string
}
// getState 是一个包内方法,供发起人使用
func (m *TextEditorMemento) getState() string {return m.state
}

2.2 步骤 2: 实现发起人

TextEditor 是发起人,它拥有状态(文本内容),并能创建和恢复备忘录。

// originator.go
package main
import "fmt"
// TextEditor 发起人:文本编辑器
type TextEditor struct {text string
}
// NewTextEditor 创建一个新的文本编辑器
func NewTextEditor() *TextEditor {return &TextEditor{text: ""}
}
// Write 写入文本,改变状态
func (e *TextEditor) Write(text string) {e.text += text
}
// GetContent 获取当前内容
func (e *TextEditor) GetContent() string {return e.text
}
// Save 创建一个备忘录,保存当前状态
func (e *TextEditor) Save() *TextEditorMemento {fmt.Printf("  [发起人] 保存状态: \"%s\"\n", e.text)return &TextEditorMemento{state: e.text}
}
// Restore 从备忘录恢复状态
func (e *TextEditor) Restore(memento *TextEditorMemento) {e.text = memento.getState()fmt.Printf("  [发起人] 恢复状态到: \"%s\"\n", e.text)
}

2.3 步骤 3: 实现负责人

History 是负责人,它负责管理备忘录的栈,实现撤销功能。

// caretaker.go
package main
import "fmt"
// History 负责人:历史记录管理器
type History struct {mementos []*TextEditorMemento
}
// NewHistory 创建一个新的历史记录
func NewHistory() *History {return &History{mementos: make([]*TextEditorMemento, 0)}
}
// Backup 保存一个备忘录
func (h *History) Backup(memento *TextEditorMemento) {h.mementos = append(h.mementos, memento)fmt.Printf("  [负责人] 备份已保存。当前历史记录数: %d\n", len(h.mementos))
}
// Undo 撤销到上一个状态
func (h *History) Undo() *TextEditorMemento {if len(h.mementos) == 0 {fmt.Println("  [负责人] 没有可撤销的历史记录。")return nil}// 获取最后一个备忘录lastIndex := len(h.mementos) - 1memento := h.mementos[lastIndex]// 从历史中移除它h.mementos = h.mementos[:lastIndex]fmt.Printf("  [负责人] 执行撤销。当前历史记录数: %d\n", len(h.mementos))return memento
}

2.4 步骤 4: 客户端代码与演示

现在,我们把所有部分组合起来,模拟用户在编辑器中输入、保存和撤销的过程。

// main.go
package main
func main() {editor := NewTextEditor()history := NewHistory()fmt.Println("--- 开始编辑 ---")editor.Write("Hello, ")fmt.Println("当前内容:", editor.GetContent())history.Backup(editor.Save()) // 第一次保存editor.Write("World!")fmt.Println("当前内容:", editor.GetContent())history.Backup(editor.Save()) // 第二次保存editor.Write(" Go is awesome.")fmt.Println("当前内容:", editor.GetContent())history.Backup(editor.Save()) // 第三次保存fmt.Println("\n--- 开始撤销 ---")// 撤销到第三个状态memento := history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("撤销后内容:", editor.GetContent())}// 再次撤销memento = history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("再次撤销后内容:", editor.GetContent())}// 再次撤销memento = history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("再次撤销后内容:", editor.GetContent())}// 尝试在空历史记录上撤销memento = history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("再次撤销后内容:", editor.GetContent())}
}

2.5 Go语言中的变体:使用序列化

当对象状态非常复杂时,手动创建备忘录结构体会很繁琐。一个更通用的方法是使用序列化(如 jsongob)将整个对象的状态转换成字节流(字符串),这就是备忘录。恢复时再反序列化回去。
这种方式下,Memento 可以简单地是一个 []bytestring

简化的序列化版本:

import ("encoding/json""fmt"
)
// 发起人增加序列化方法
func (e *TextEditor) SaveToString() (string, error) {data, err := json.Marshal(e)if err != nil {return "", err}return string(data), nil
}
func (e *TextEditor) RestoreFromString(data string) error {// 注意:这里为了简化,直接覆盖了e,实际中可能需要更精细的字段赋值var newEditor TextEditorerr := json.Unmarshal([]byte(data), &newEditor)if err == nil {e.text = newEditor.text}return err
}
// 负责人管理字符串切片
type StringHistory struct {states []string
}
func (h *StringHistory) Backup(state string) {h.states = append(h.states, state)
}
func (h *StringHistory) Undo() string {if len(h.states) == 0 {return ""}lastIndex := len(h.states) - 1state := h.states[lastIndex]h.states = h.states[:lastIndex]return state
}

这种方法的优点是通用性强,缺点是性能开销比直接内存拷贝大,且需要确保对象的所有字段都是可序列化的。

2.6 完整代码

为了方便运行,将所有代码都放在了一个 main.go 文件中。代码结构遵循了之前讲解的三个角色:发起人、备忘录和负责人。

// main.go
package main
import "fmt"
// ==================== 1. 备忘录 ====================
// TextEditorMemento 文本编辑器的备忘录
// 注意:state字段是小写的,意味着它只能在当前包内被访问,从而保护了封装性。
type TextEditorMemento struct {state string
}
// getState 是一个包内方法,供发起人使用
func (m *TextEditorMemento) getState() string {return m.state
}
// ==================== 2. 发起人 ====================
// TextEditor 发起人:文本编辑器
type TextEditor struct {text string
}
// NewTextEditor 创建一个新的文本编辑器
func NewTextEditor() *TextEditor {return &TextEditor{text: ""}
}
// Write 写入文本,改变状态
func (e *TextEditor) Write(text string) {e.text += text
}
// GetContent 获取当前内容
func (e *TextEditor) GetContent() string {return e.text
}
// Save 创建一个备忘录,保存当前状态
func (e *TextEditor) Save() *TextEditorMemento {fmt.Printf("  [发起人] 保存状态: \"%s\"\n", e.text)return &TextEditorMemento{state: e.text}
}
// Restore 从备忘录恢复状态
func (e *TextEditor) Restore(memento *TextEditorMemento) {e.text = memento.getState()fmt.Printf("  [发起人] 恢复状态到: \"%s\"\n", e.text)
}
// ==================== 3. 负责人 ====================
// History 负责人:历史记录管理器
type History struct {mementos []*TextEditorMemento
}
// NewHistory 创建一个新的历史记录
func NewHistory() *History {return &History{mementos: make([]*TextEditorMemento, 0)}
}
// Backup 保存一个备忘录
func (h *History) Backup(memento *TextEditorMemento) {h.mementos = append(h.mementos, memento)fmt.Printf("  [负责人] 备份已保存。当前历史记录数: %d\n", len(h.mementos))
}
// Undo 撤销到上一个状态
func (h *History) Undo() *TextEditorMemento {if len(h.mementos) == 0 {fmt.Println("  [负责人] 没有可撤销的历史记录。")return nil}// 获取最后一个备忘录lastIndex := len(h.mementos) - 1memento := h.mementos[lastIndex]// 从历史中移除它h.mementos = h.mementos[:lastIndex]fmt.Printf("  [负责人] 执行撤销。当前历史记录数: %d\n", len(h.mementos))return memento
}
// ==================== 4. 客户端代码与演示 ====================
func main() {editor := NewTextEditor()history := NewHistory()fmt.Println("--- 开始编辑 ---")editor.Write("Hello, ")fmt.Println("当前内容:", editor.GetContent())history.Backup(editor.Save()) // 第一次保存editor.Write("World!")fmt.Println("当前内容:", editor.GetContent())history.Backup(editor.Save()) // 第二次保存editor.Write(" Go is awesome.")fmt.Println("当前内容:", editor.GetContent())history.Backup(editor.Save()) // 第三次保存fmt.Println("\n--- 开始撤销 ---")// 撤销到第三个状态memento := history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("撤销后内容:", editor.GetContent())}// 再次撤销memento = history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("再次撤销后内容:", editor.GetContent())}// 再次撤销memento = history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("再次撤销后内容:", editor.GetContent())}// 尝试在空历史记录上撤销fmt.Println("\n--- 尝试在空历史记录上撤销 ---")memento = history.Undo()if memento != nil {editor.Restore(memento)fmt.Println("再次撤销后内容:", editor.GetContent())}
}

2.7 执行结果和分析

程序运行后,你将在终端看到以下输出:

--- 开始编辑 ---
当前内容: Hello, [发起人] 保存状态: "Hello, "[负责人] 备份已保存。当前历史记录数: 1
当前内容: Hello, World![发起人] 保存状态: "Hello, World!"[负责人] 备份已保存。当前历史记录数: 2
当前内容: Hello, World! Go is awesome.[发起人] 保存状态: "Hello, World! Go is awesome."[负责人] 备份已保存。当前历史记录数: 3
--- 开始撤销 ---[负责人] 执行撤销。当前历史记录数: 2[发起人] 恢复状态到: "Hello, World! Go is awesome."
撤销后内容: Hello, World! Go is awesome.[负责人] 执行撤销。当前历史记录数: 1[发起人] 恢复状态到: "Hello, World!"
再次撤销后内容: Hello, World![负责人] 执行撤销。当前历史记录数: 0[发起人] 恢复状态到: "Hello, "
再次撤销后内容: Hello, 
--- 尝试在空历史记录上撤销 ---[负责人] 没有可撤销的历史记录。

结果分析:

  1. 编辑与备份阶段
    • 用户每次输入文本后,editor.Save() 都会创建一个包含当前状态的 TextEditorMemento 对象。
    • history.Backup() 将这个备忘录对象存入一个历史记录栈中。
    • 这个过程清晰地展示了发起人创建状态,负责人保存状态的职责分离。
  2. 撤销阶段
    • 每次调用 history.Undo(),负责人都会从栈顶弹出一个备忘录,并将其返回。
    • editor.Restore() 接收到这个备忘录后,使用其内部状态来恢复自己的 text 字段。
    • 编辑器的内容成功地回退到了上一个保存的版本。
  3. 边界情况
    • 当历史记录为空时,再次调用 Undo(),负责人会友好地提示“没有可撤销的历史记录”,并返回 nil,避免了程序错误。

这个例子完美地演示了备忘录模式如何在不暴露 TextEditor 内部实现的情况下,实现了状态的保存和恢复功能。

总结:备忘录模式是Go中实现状态快照和撤销功能的理想选择。

  • 对于状态简单、性能要求高的场景,使用结构体作为备忘录是最佳选择,利用Go的包内可见性来保证封装。
  • 对于状态复杂、需要通用性的场景,使用序列化作为备忘录的实现方式更加灵活。

理解备忘录模式的关键在于分清三个角色的职责:发起人只关心自己的业务和状态的创建/恢复,负责人只关心备忘录的存储和管理,而备忘录本身则是一个被动的数据容器。这种职责分离使得代码结构清晰,易于维护。

http://www.dtcms.com/a/564487.html

相关文章:

  • 基于YOLOv10的无人机智能巡检系统:电力线路悬挂物检测实战
  • 定制开发开源AI智能名片S2B2C商城小程序中的羊群效应应用研究
  • seo搜索引擎优化网站店铺位置怎么免费注册定位
  • 一个专门做恐怖片的网站做化工行业网站
  • 物联网 “神经” 之以太网:温湿度传感器的工业级 “高速干道”​
  • Biotin-PEG-OH,生物素-聚乙二醇-羟基,应用领域
  • 物联网“神经”之LoRa:温湿度传感器的广域“节能使者”
  • 舆情处置的自动化实践:基于Infoseek舆情系统的技术解析与落地指南
  • jcms内容管理系统百度seo怎么查排名
  • 亚马逊旺季广告攻略:解码产品周期,精准引爆销量
  • 【C#】HTTP中URL编码方式解析
  • 高速打印,安全稳定全兼顾 至像国产芯系列M3500DNWA应用测评
  • MacOS 安装Python 3.13【同时保留旧版本】
  • 八股训练营第 6 天 | HTTPS 和HTTP 有哪些区别?HTTPS的工作原理(HTTPS建立连接的过程)?TCP和UDP的区别?
  • 多阶段构建:打造最小化的 Spring Boot Docker 镜像
  • 在windows或者mac配置安装miniforge3
  • Arbess零基础学习 - 使用Arbess+GitPuk实现Java项目构建并Docker部署
  • 网站注册备案之后怎么做营销网络平台
  • laya3如何打包mac包
  • 【Linux】从内存布局到信号屏蔽:Linux 内核态与用户态交互核心知识点汇总
  • Docker:创建自定义容器,附通用Python 3.12模板
  • 从开源到智能体:OpenCSG 的长期主义
  • 合肥的网站建设州满分企业网
  • java通过模板渲染PDF报告
  • 基于 Verl 前端与 Atlas A3 集群的 DeepSeek-R1 模型 RL 训练优化实践:Cann-recipes-train 仓库技术深度解读
  • 技术评测:六行神算大模型平台实战分析
  • SecureShellProtocol(ssh)
  • HAproxy负载均衡详细介绍
  • Rust编程学习 - 如何快速构建一个单线程 web server
  • 1、PCB导入Siwave并设置叠层数据