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

重修设计模式-行为型-备忘录模式

重修设计模式-行为型-备忘录模式

Captures and externalizes an object’s internal state so that it can be restored later, all without violating encapsulation.

在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。

备忘录模式(Memento Pattern)允许在不暴露对象实现细节的情况下保存和恢复对象的状态。该模式的主要目的是通过捕获和存储对象的内部状态,以便将来能够将对象恢复到这个状态。

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

  1. 发起者(Originator):负责创建一个备忘录,并且可以记录、恢复自身的内部状态。可以根据需要决定 Memento 保存自身的那些内部状态。

  2. 备忘录(Memento):存储 Originator 内部状态的快照,防止 Originator 以外的的对象访问 Memento。

  3. 管理者(Caretaker):负责存储备忘录,但不能检查或操作备忘录的内容。

举个例子,现在需要完成一个文本编辑器,在输入文本时,程序需要将内容追加存储在内存文本中,在输入:undo命令后,会执行撤销操作,该操作会将保存的文本内容撤销到上一次输入状态。

对于该需求,首先需要创建的是编辑器类(Editor),这是编辑器需求的承载类。

class Editor {
    private val text: StringBuilder = StringBuilder()

    fun append(input: String) {
        this.text.append(input)
    }

    fun getText(): String = text.toString()
}

Editor 类只对外暴露 append 和 getText 方法,用于拼接内容和获取内容,符合迪米特法则,不该暴露的内部细节就不暴露。

其次需要考虑备份和恢复问题,回到备忘录模式,Editor 其实相当于 Originator 角色,这时还需要创建 Memento 角色来存储 Originator 的内部状态快照。这里需要思考两个问题:

1.能否直接复用 Editor 类作为快照的内部状态?

//如:
class Snapshot(private var editor: Editor) {
    fun getEditor() = editor
}

//恢复时:Editor暴露set方法,便于从快照恢复状态
class Editor {
    //
    fun setText(editor: Editor) {
        this.text.replace(0, this.text.length, editor.getText())
    }
    ...
}

直接复用 Editor 类作为内部状态,创建快照时通过深拷贝实现,好处是 Editor 需要备份的属性不用重写一份,不过缺点也显而易见:

  • 第一,为了能用快照恢复 Editor 对象,需要在 Editor 内部定义 setText() 函数,但这个函数是公用的,可能会被其他业务使用,暴露不应该暴露的函数,违背封装原则。
  • 第二,快照本身是不可变的,但上面的快照复用了业务模型 Editor 类的定义,而 Editor 类本身有一系列修改内部状态的函数,同样违背封装原则。
  • 第三,复用 Editor 意味着无法灵活选择要备份的属性,即使通过深浅拷贝的实现来达到目的(需要备份的深拷贝,不需要备份的浅拷贝或不拷贝),也会存在数据冗余问题。

2.在哪里进行快照的生成和恢复?

快照的创建和恢复方法只能在 Originator 角色内部定义,如果在 Originator 外部,就需要暴露类的所有内部细节而使其过于脆弱;如果对外屏蔽类的内部细节又无法生成快照,所以快照的生成和恢复只能由 Originator 自身来实现。这也是备忘录模式“不暴露对象实现细节的情况下,保存和恢复对象的状态”的核心。

Caretaker 角色的定义非常简答, 这里创建 SnapshotHolder 类,并在内部通过栈结构来存储历史快照。

上面的例子使用备忘录模式实现的完整代码如下:

在这里插入图片描述

//Originator:文本编辑器
class Editor {
    private val text: StringBuilder = StringBuilder()

    fun append(input: String) {
        this.text.append(input)
    }

    fun getText(): String = text.toString()

    //--生成快照--
    fun createSnapshot(): Snapshot {
        return Snapshot(text.toString())
    }

    //--从快照恢复--
    fun restoreSnapshot(snapshot: Snapshot) {
        this.text.replace(0, this.text.length, snapshot.getText())
    }
}

//Memento:快照
class Snapshot(private val text: String) {
    fun getText() = text
}

//Caretaker:存储快照
class SnapshotHolder {
    private val snapshots: Stack<Snapshot> = Stack()

    fun popSnapshot(): Snapshot? {
        return if (snapshots.isEmpty()) null else snapshots.pop()
    }

    fun pushSnapshot(snapshot: Snapshot) {
        snapshots.push(snapshot)
    }
}

fun main() {
    println("please input:")
    val editor = Editor()
    val snapshotHolder = SnapshotHolder()
    val scanner = Scanner(System.`in`)
    while (scanner.hasNext()) {
        val input: String = scanner.next()
        if (input == ":undo") {
            snapshotHolder.popSnapshot()?.let { snapshot ->
                editor.restoreSnapshot(snapshot)  //从快照恢复上一状态
            } ?: println("无存储的快照了!")
        } else {
            //在发起者内部状态发生变化之前,创建快照对象,将内部状态保存。
            snapshotHolder.pushSnapshot(editor.createSnapshot())
            editor.append(input)
        }
        println("current content:${editor.getText()}")
    }
}

代码执行结果:

在这里插入图片描述

如何优化内存和时间消耗?

对于大对象的备份来说,备份占用的存储空间会比较大,备份和恢复的耗时会比较长。针对这个问题,不同的业务场景有不同的处理方式。比如,只备份必要的恢复信息,结合最新的数据来恢复;再比如,全量备份和增量备份相结合,低频全量备份,高频增量备份,两者结合来做恢复,具体思路如下:

当需要恢复到某一时间点的备份的时候,如果这一时间点有做全量备份,直接拿来恢复即可。如果这一时间点没有对应的全量备份,就先找到最近的一次全量备份,然后用它来恢复,之后执行此次全量备份跟这一时间点之间的所有增量备份,也就是对应的操作或者数据变动。这样就能减少全量备份的数量和频率,减少对时间、内存的消耗。

总结

备忘录模式通过封装对象的内部状态,提供了一种灵活且安全的状态管理机制,非常适合用于需要保存和恢复状态的复杂系统中,比如在需要实现撤销操作或保存和恢复对象状态的场景,例如文本编辑器、游戏状态保存等。


http://www.kler.cn/a/399587.html

相关文章:

  • HuggingFace:基于YOLOv8的人脸检测模型
  • Vulnhub靶场案例渗透[9]- HackableIII
  • Java基础-集合
  • 第 14 章 -Go语言 错误处理
  • 计算机组成与原理(2) basic of computer architecture
  • web——upload-labs——第三关——后缀黑名单绕过
  • 计算机网络基础——针对实习面试
  • Rust:AtomicI8 还是 Mutex<u8>?
  • 网络延迟对Python爬虫速度的影响分析
  • cJson移植使用
  • 计算机组成与原理(2) basic of computer architecture
  • STM32 极速入门第一天基础拓展 驱动i2c屏幕 ( 使用PlatformIO开发STM32单片机 )
  • 文献阅读11.17
  • 半导体工艺与制造篇1 绪论
  • 【WPF】Prism学习(二)
  • ThinkPHP中的MVC分层是什么
  • 鸿蒙生态的认知和生态的崛起分析
  • 表面法线估计(Surface Normal Estimation)
  • 【机器学习】机器学习中用到的高等数学知识-5. 函数空间和泛函分析 (Functional Analysis)
  • PostgreSQL 并行计算算法,参数,强制并行度设置
  • 使用Web Components构建模块化Web应用
  • 【电子设计】按键LED控制与FreeRTOS
  • 万字长文解读机器学习——降维
  • PCL 点云分割 欧式聚类算法分割
  • vs2022搭建opencv开发环境
  • 力扣62.不同路径