• 设计模式:快照模式


    快照模式也叫做备忘录模式,但是我觉得如果是了解快照的话,我觉得比备忘录更形象一点,毕竟现在快照有各种,有页面快照,有系统快照等,相当于是一个备份。备忘录其实也是备份的意思,我觉得快照更贴切一点现在的描述。

    快照模式是一种行为模式,行为模式可以说是设计模式最大头的一部分,作为行为的解耦,相比于创建型,结构型,实际上不改架构的单纯做业务我觉得行为模式更多一些,搭配别的模式花样更多。

    快照或者说备忘录模式的代码实现比较灵活(就是满足要求满足定义就可以,不强调实现的结构),应用场景也比较明确和有限,主要是用来防丢失、撤销、恢复等。

    GoF的备忘录模式定义: Memento Design Pattern

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

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

    涉及到两个问题:
    为什么存储和恢复副本会违背封装原则?
    备忘录模式是如何做到不违背封装原则的?

    有一个例子可以对应上面的问题

    假设有这样一道面试题,希望你编写一个小程序,可以接收命令行的输入。用户输入文本时,程序将其追加存储在内存文本中;用户输入“:list”,程序在命令行中输出内存文本的内容;用户输入“:undo”,程序会撤销上一次输入的文本,也就是从内存文本中将上次输入的文本删除掉。

    结果大概是这样的

    
    >hello
    >:list
    hello
    >world
    >:list
    helloworld
    >:undo
    >:list
    hello
    

    第一种存储和恢复副本的形式

    
    public class InputText {
      private StringBuilder text = new StringBuilder();
    
      public String getText() {
        return text.toString();
      }
    
      public void append(String input) {
        text.append(input);
      }
    
      public void setText(String text) {
        this.text.replace(0, this.text.length(), text);
      }
    }
    
    public class SnapshotHolder {
      private Stack snapshots = new Stack<>();
    
      public InputText popSnapshot() {
        return snapshots.pop();
      }
    
      public void pushSnapshot(InputText inputText) {
        InputText deepClonedInputText = new InputText();
        deepClonedInputText.setText(inputText.getText());
        snapshots.push(deepClonedInputText);
      }
    }
    
    public class ApplicationMain {
      public static void main(String[] args) {
        InputText inputText = new InputText();
        SnapshotHolder snapshotsHolder = new SnapshotHolder();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
          String input = scanner.next();
          if (input.equals(":list")) {
            System.out.println(inputText.getText());
          } else if (input.equals(":undo")) {
            InputText snapshot = snapshotsHolder.popSnapshot();
            inputText.setText(snapshot.getText());
          } else {
            snapshotsHolder.pushSnapshot(inputText);
            inputText.append(input);
          }
        }
      }
    }
    

    那问题来了

    第一,为了能用快照恢复 InputText 对象,我们在 InputText 类中定义了 setText() 函数,但这个函数有可能会被其他业务使用,所以,暴露不应该暴露的函数违背了封装原则;

    第二,快照本身是不可变的,理论上讲,不应该包含任何 set() 等修改内部状态的函数,但在上面的代码实现中,“快照“这个业务模型复用了 InputText 类的定义,而 InputText 类本身有一系列修改内部状态的函数,所以,用 InputText 类来表示快照违背了封装原则。

    备忘录实现

    
    public class InputText {
      private StringBuilder text = new StringBuilder();
    
      public String getText() {
        return text.toString();
      }
    
      public void append(String input) {
        text.append(input);
      }
    
      public Snapshot createSnapshot() {
        return new Snapshot(text.toString());
      }
    
      public void restoreSnapshot(Snapshot snapshot) {
        this.text.replace(0, this.text.length(), snapshot.getText());
      }
    }
    
    public class Snapshot {
      private String text;
    
      public Snapshot(String text) {
        this.text = text;
      }
    
      public String getText() {
        return this.text;
      }
    }
    
    public class SnapshotHolder {
      private Stack snapshots = new Stack<>();
    
      public Snapshot popSnapshot() {
        return snapshots.pop();
      }
    
      public void pushSnapshot(Snapshot snapshot) {
        snapshots.push(snapshot);
      }
    }
    
    public class ApplicationMain {
      public static void main(String[] args) {
        InputText inputText = new InputText();
        SnapshotHolder snapshotsHolder = new SnapshotHolder();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
          String input = scanner.next();
          if (input.equals(":list")) {
            System.out.println(inputText.toString());
          } else if (input.equals(":undo")) {
            Snapshot snapshot = snapshotsHolder.popSnapshot();
            inputText.restoreSnapshot(snapshot);
          } else {
            snapshotsHolder.pushSnapshot(inputText.createSnapshot());
            inputText.append(input);
          }
        }
      }
    }
    

    我这里是一个我自己添加了一个退出机制,就是:exit的代码改写。
    快照模式演示demo

    相当于是创建一个快照类来存快照,和inputext做一个解耦,inputtext依旧负责字符串的操作和保存,只不过这个备份由snapshot。现实中我们很多设计会涉及到备份的概念,但是考虑到备份太多占用空间以及IO,基本是低频全量备份,高频增量备份来解决。

  • 相关阅读:
    【CSDN|每日一练】求最小元素
    【Rust日报】2022-08-14 Actix Web 的可扩展速率限制中间件
    vue 后端返回二进制流-前端通过blob对象下载文件-图片
    YOLOv3~
    pandas教程:Combining and Merging Datasets 合并数据集
    ESP8266/ESP32 +1.3“ or 0.96“ IIC OLED指针式时钟
    Cholesterol-PEG-DBCO,CLS-PEG-DBCO,胆固醇-聚乙二醇-二苯基环辛炔PEG衍生物
    仿函数的学习
    Solidity学习-投票合约示例
    1万多条健康饮食厨房宝典ACCESS\EXCEL数据库
  • 原文地址:https://blog.csdn.net/FeiChangWuRao/article/details/126950055