likes
comments
collection
share

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

作者站长头像
站长
· 阅读数 24

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述

概述

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

要查看所有设计模式的实际操作,请查看 Flutter Design Patterns 应用程序

什么是备忘录设计模式?

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

备忘录,也被称为令牌,属于行为型设计模式。这个设计模式的目的在 GoF 书籍中被描述为:

不违反封装的情况下,捕获并外化一个对象的内部状态,以便稍后可以将该对象恢复到此状态。

这个模式的关键思想是让一个对象(发起人)自己负责保存和恢复其内部状态。内部状态(其快照)保存在另一个对象中——一个备忘录。当需要恢复发起人的内部状态时,撤销机制会向发起人请求备忘录。负责保存和恢复发起人内部状态的客户端(负责人)存储了一系列备忘录对象,以便可以将备忘录传回发起人以恢复到之前的状态。但是,负责人本身不允许访问或修改备忘录——只有创建特定备忘录的发起人对象被允许这样做。

为了更好地理解备忘录设计模式,让我们通过分析其结构和实现来深入了解!

分析

备忘录设计模式的一般结构如下所示:

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

  • 备忘录 - 声明一个接口,限制对 具体备忘录 字段的访问,只声明与备忘录元数据相关的方法,这个接口被 负责人 用来处理 具体备忘录 对象;
  • 具体备忘录 - 存储 发起人 的内部状态。同时,防止除了创建 具体备忘录发起人 之外的对象访问。
  • 负责人 - 负责 备忘录 的安全保管,永远不操作或检查 备忘录 的内容。
  • 发起人 - 创建包含其当前内部状态快照的 具体备忘录。同时,提供 restore() 方法使用 具体备忘录 恢复内部状态。

适用性

当你想要生成对象状态的快照以便能够恢复对象以前的状态时,应该使用备忘录设计模式。备忘录模式允许你制作对象状态的完整副本,包括私有字段,并将它们与对象分开存储。

此外,出于安全原因,当直接访问对象的字段/获取器/设置器违反其封装时,也可以使用该模式。备忘录使对象自己负责创建其状态的快照。没有其他对象可以读取快照,使原始对象的状态数据安全且安全。

实现

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

为了实现备忘录设计模式并展示其优点,我们将在命令设计模式的示例上进一步工作。因此,如果您错过了上一篇文章,我强烈建议您现在查看其实现部分。

示例的主要思想保持不变 - 我们将创建一个非常简单的虚构图形编辑器。为了简化命令设计模式的部分,示例的 UI 中只创建并可用一个命令 - RandomisePropertiesCommand。这个命令随机化形状对象的所有属性 - 高度、宽度和颜色 - 这充当我们示例的状态。

显然,与之前的实现不同的是 - 添加了备忘录设计模式。在实现命令设计模式的示例时,我们将其状态(形状对象)存储在示例组件本身中。这一次,状态存储在发起人对象内部,只能由它来操纵。RandomisePropertiesCommand 充当负责人对象,将发起人状态的先前快照存储在备份属性中。备份属性只不过是发起人在执行命令之前创建的 Memento 对象。

使用备忘录设计模式的结果是,示例的状态被封装并移出了示例组件。此外,可以从其备忘录快照中恢复命令的 undo() 操作的先前状态。在这种情况下,备忘录设计模式扩展了命令设计模式,并与之非常好地协作。

在实现备忘录设计模式并将其集成到我们的示例之前,让我们先检查类图,然后研究其组件。

类图

下面的类图展示了备忘录设计模式的实现:

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

ICommand 定义了特定命令的通用接口:

  • execute() - 执行命令;
  • undo() - 撤销命令并将状态恢复到之前的快照。

RandomisePropertiesCommand 是一个实现了 ICommand 接口的具体命令。

CommandHistory 是一个简单的类,存储了已执行命令的列表(commandList),并提供方法向命令历史列表中添加新命令(add())以及撤销列表中的最后一个命令(undo())。

IMemento 定义了特定备忘录类的通用接口:

  • getState() - 返回发起人内部状态的快照。

Memento 是一个充当发起人内部状态快照的类,该状态存储在 state 属性中,并通过 getState() 方法返回。

Shape 是一个简单的数据类,用作发起人的内部状态。它存储了定义 UI 中展示的形状的多个属性:colorheightwidth

Originator - 一个简单的类,包含其内部状态,并使用 createMemento() 方法将其状态的快照存储到 Memento 对象中。此外,发起人的状态可以通过提供的 Memento 对象恢复,使用 restore() 方法。

MementoExample 初始化并包含 CommandHistoryOriginator 对象。此组件还包含一个分配有 RandomisePropertiesCommand 命令的 PlatformButton 小部件。当按钮被按下时,命令被执行并添加到存储在 CommandHistory 对象中的命令历史列表中。

Shape

一个简单的类,用于存储有关形状的信息:颜色、高度和宽度。此类还包含几个构造函数:

  • Shape() - 基本构造函数,用于创建具有提供的值的形状对象;
  • Shape.initial() - 命名构造函数,用于创建具有预定义初始值的形状对象;
  • Shape.copy() - 命名构造函数,用于创建作为提供的 Shape 值副本的形状对象。
class Shape {
  Shape.initial()
      : color = Colors.black,
        height = 150.0,
        width = 150.0;

  Shape.copy(Shape shape)
      : color = shape.color,
        height = shape.height,
        width = shape.width;

  Color color;
  double height;
  double width;
}

ICommand

一个定义特定命令类应实现的方法的接口。

abstract interface class ICommand {
  void execute();
  void undo();
}

RandomisePropertiesCommand

一种特定的命令实现,将存储在 Originator 中的 Shape 对象的所有属性设置为随机值。此类还实现了 undo 操作。

class RandomisePropertiesCommand implements ICommand {
  RandomisePropertiesCommand(this.originator)
      : _backup = originator.createMemento();

  final Originator originator;
  final IMemento _backup;

  @override
  void execute() {
    final shape = originator.state;

    shape.color = Color.fromRGBO(
      random.integer(255),
      random.integer(255),
      random.integer(255),
      1.0,
    );
    shape.height = random.integer(150, min: 50).toDouble();
    shape.width = random.integer(150, min: 50).toDouble();
  }

  @override
  void undo() => originator.restore(_backup);
}

CommandHistory

一个简单的类,存储已执行命令的列表。此类还提供了 isEmpty 获取方法,如果命令历史列表为空则返回 true。新命令可以通过 add() 方法添加到命令历史列表中,如果命令历史列表不为空,可以使用 undo() 方法撤销最后一个命令。

class CommandHistory {
  final _commandList = ListQueue<ICommand>();

  bool get isEmpty => _commandList.isEmpty;

  void add(ICommand command) => _commandList.add(command);

  void undo() {
    if (_commandList.isEmpty) return;

    _commandList.removeLast().undo();
  }
}

IMemento

一个定义 getState() 方法的接口,由特定的备忘录类实现。

abstract interface class IMemento {
  Shape getState();
}

Memento

实现 IMemento 接口的类,存储 Originator 内部状态(Shape 对象)的快照。状态可以通过 getState() 方法供 Originator 访问。

class Memento implements IMemento {
  Memento(Shape shape) : _state = Shape.copy(shape);

  final Shape _state;

  @override
  Shape getState() => _state;
}

Originator

一个定义 createMemento() 方法的类,用于将当前内部状态保存到一个 Memento 对象中。

class Originator {
  Originator() : state = Shape.initial();

  Shape state;

  IMemento createMemento() => Memento(state);

  void restore(IMemento memento) => state = memento.getState();
}

Example

首先,准备了一个 markdown 文件并作为模式描述提供:

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

MementoExample 包含 CommandHistoryOriginator 对象。此组件还包含一个 PlatformButton 组件,它使用 RandomisePropertiesCommand 来随机化形状的属性值。命令执行后,它被添加到存储在 CommandHistory 对象中的命令历史列表中。如果命令历史不为空,撤销 按钮将被启用,可以撤销最后一个命令。

class MementoExample extends StatefulWidget {
  const MementoExample();

  @override
  _MementoExampleState createState() => _MementoExampleState();
}

class _MementoExampleState extends State<MementoExample> {
  final _commandHistory = CommandHistory();
  final _originator = Originator();

  void _randomiseProperties() {
    final command = RandomisePropertiesCommand(_originator);
    _executeCommand(command);
  }

  void _executeCommand(ICommand command) => setState(() {
        command.execute();
        _commandHistory.add(command);
      });

  void _undo() => setState(() => _commandHistory.undo());

  @override
  Widget build(BuildContext context) {
    return ScrollConfiguration(
      behavior: const ScrollBehavior(),
      child: SingleChildScrollView(
        padding: const EdgeInsets.symmetric(
          horizontal: LayoutConstants.paddingL,
        ),
        child: Column(
          children: <Widget>[
            ShapeContainer(
              shape: _originator.state,
            ),
            const SizedBox(height: LayoutConstants.spaceM),
            PlatformButton(
              materialColor: Colors.black,
              materialTextColor: Colors.white,
              onPressed: _randomiseProperties,
              text: 'Randomise properties',
            ),
            const Divider(),
            PlatformButton(
              materialColor: Colors.black,
              materialTextColor: Colors.white,
              onPressed: _commandHistory.isEmpty ? null : _undo,
              text: 'Undo',
            ),
            const SizedBox(height: LayoutConstants.spaceM),
          ],
        ),
      ),
    );
  }
}

正如您在这个示例中看到的,客户端代码(UI 元素、命令历史等)并没有与任何特定的命令类耦合,因为它通过 ICommand 接口与命令交互。

除了命令设计模式为这个示例提供的功能外,备忘录设计模式还为示例的状态添加了额外的层。状态存储在 Originator 对象内部,命令本身不直接改变状态,而是通过 Originator。此外,存储在 Command 内部的备份(状态快照)是一个 Memento 对象,而不是状态(Shape 对象)本身 - 如果触发了状态恢复(命令上的撤销操作),特定命令会调用 Originator 上的 restore() 方法,将其内部状态恢复到快照中存储的值。因此,它允许在单个请求中恢复多个属性值(一个复杂的状态对象),同时状态本身完全与命令的代码或UI逻辑分离。

关于备忘录设计模式及其在 Dart 和 Flutter 中的实现的概述关于备忘录设计模式及其在 Dart 和 Flutt

正如您在示例中看到的,当命令执行时,实际上存储了发起人内部状态的快照,稍后可以通过在命令上执行撤销操作来恢复。

所有备忘录设计模式及其示例实现的代码更改可以在这里找到。

要查看模式的实际应用,请查看交互式备忘录示例

转载自:https://juejin.cn/post/7370640471394205706
评论
请登录