5

我需要保存 Java 应用程序中一些操作的状态历史记录,以便稍后重新加载以恢复某个操作的状态。换句话说,我有一个屏幕,它有一个与之关联的状态,我需要存储它以及历史记录中的任何更改,以便我可以随时恢复屏幕的状态。这有点像“撤消”,但不完全是因为两个状态之间的差异可能非常大,并且没有明确定义的动作可以改变状态。

让我用一个例子来解释一下:一个非常基本的屏幕状态可能只包含一个地图。在状态 A 中,此 Map 包含对具有键“Key1”的“Object1”和具有键“Key2”的“Object2”的引用。在状态 B 中,地图仍然包含对“Object1”的引用,但“Object2”已被修改并添加了“Object3”。我现在需要能够返回到状态 A,这将涉及“删除”Object3 并将 Object2 恢复到之前的状态。我无法定义任何自定义“撤消操作”,因为我不知道对 Object2 进行了哪些更改,甚至不知道 Object2 的类型是什么。此外,由于在状态 A 和 B 中 Object2 的引用保持不变,因此这些更改会反映在状态 A 中,因此 Object2 与以前不同。

我意识到最好的解决方案是实现克隆方法,但是因为我需要支持所有类型的对象(包括原语和标准集合),所以这是不可行的。我考虑过使用可序列化,一旦发生状态转换,我会序列化 Map,然后在再次需要时反序列化它,但这似乎是一个非常丑陋的解决方案。

有人有其他想法吗?谢谢你,里斯特雷托

4

7 回答 7

15

您是否尝试过研究Memento 设计模式?它似乎特别适合您的问题。来自维基百科:

备忘录模式是一种软件设计模式,它提供了将对象恢复到其先前状态(通过回滚撤消)的能力。

同一页面还有一个包含Java 实现的部分,因为您提到这是用 Java 编写的。

于 2009-02-03T07:19:11.160 回答
2

考虑换个角度。不要改变构成屏幕状态的对象,而是使用不可变状态。这听起来可能有点矛盾,但事实并非如此。

例如,假设(为简单起见)您的状态由单个字符串组成。显然,由于字符串是不可变的,因此您不必为了保存和修改状态而克隆字符串。例如:

public List<String> changeTheScreen(List<String> states) {
  return states.cons(states.head() + "x");
}

public void renderTheScreen(String currentState) {
  // TODO: draw the screen given the current state
}

在上面的示例中,Listis 是来自Functional Javafj.data.List库的不可变内存中单链表类型(标准库没有不可变列表)。该方法将获取状态的历史记录,当前状态位于列表的前面。它通过创建一个新状态并将其放在新状态列表的前面来操纵屏幕的状态。

将相同的原则应用于您想要用作状态的任何类型。确保您的状态完全由不可变对象组成(字符串和原语已经是不可变的)。将不可变对象用于状态将为您节省很多维护方面的麻烦,并节省内存,因为不可变对象可以重复使用而无需克隆。

不可变对象将在其构造函数中初始化,其所有内部字段将是final.

函数式 Java 有一个称为 TreeMap 的不可变映射。您可以通过以下方式使用它:

public List<TreeMap<String, Object>>
changeState(List<TreeMap<String, Object>> states) {
  return states.cons(states.head().set("Key1", new Object1("x")));
}
于 2009-02-03T08:37:13.390 回答
2

你可能想看看Prevayler

于 2009-02-03T07:26:54.063 回答
0

在我的项目中,我们通过序列化为 XML 文件实现了非常相似的效果。这对我们很有效。您想要取回的所有对象 - 以明确定义的方式在 XML 文件中序列化,以便之后您可以随时从 XML 文件取回状态。

于 2009-02-03T09:05:40.150 回答
0

我们用序列化做类似的事情。

我们以序列化的形式将归档数据存储到文件系统中。我们需要恢复的对象图部分和主对象一样是序列化的。

确保您对对象进行版本化,并确保您的差异可以处理缺失/新字段。

我们选择存储到文件系统是因为它(有效地)为我们提供了无限的容量。速度对我们来说不是问题,但文件系统方法非常快,大多数人没有注意到额外的 50-100 毫秒!

于 2009-02-03T07:15:58.937 回答
0

你总是可以使用序列化;

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.flush();
byteArrayOutputStream.close();
ByteArrayInputStream istream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream= new ObjectInputStream(istream);
Object deserialized = objectInputStream.readObject();
istream.close();

缓慢而笨重,但有效。

于 2009-02-03T07:17:56.170 回答
0

如果您想要的实际上是一张地图,您可能需要研究持久数据结构;例如,持久性 B 树。

于 2009-02-03T07:36:48.130 回答