"日志记录"还是"事务"设计模式?

9
我希望实现一个对象,即它具有日志记录或持久性事务。也就是说,该对象包含数据(可能是Map)。当对数据进行更改时,这些更改将被单独保留,沙盒化,以便任何外部对象可以引用基本状态(更改之前)或访问最新数据。然后,还有另一个操作将更改提交到基本状态。
这让我想起了Linux日志文件系统。文件系统更改会写入日志,并且只在稍后提交到永久存储器中。
这也可能更类似于关系数据库世界中的“事务”概念;也就是说,您有一些数据,您开始一个事务并以某种方式操作数据。并发进程将看到旧数据,而没有您的更改。然后,您可以“回滚”事务或“提交”您的更改。
我特别希望在Java中实现此功能,但显然这是一种通用的面向对象模式,如果存在的话。我希望至少可以创建它,但我不确定最佳实现方法。
此外,请假设该对象保存了大量数据,整个层次结构(子对象等)。因此,不能仅保留整个数据树的两个副本;这将浪费很多内存,并且复制操作(在提交时)需要太长时间。我希望在游戏上下文中实现这一点,每帧进行一次提交操作,因此它真的需要是最优的。
4个回答

8
实现您想要的最佳方式是使对象及其所有子对象都不可变。然后,两个线程就可以在没有任何冲突的情况下操作它们,您也不必维护所有内容的两个副本。唯一需要两个副本的是实际更改的内容,而这些内容可能非常少。
假设对象 A 由对象 B 和 C 组成。对象 B 由对象 D 和 E 组成。对象 C 由对象 F 和 G 组成。因此,A、B 和 C 只是两个指针,而 D、E、F 和 G 则是它们本身。
首先创建初始实例并将其提供给两个线程。
ThreadOne -> A1{ B1{ D1, E1 } C1{ F1, G1 } } 
ThreadTwo -> A1{ B1{ D1, E1 } C1{ F1, G1 } } 

因此,两个线程都指向同一个实例,不会消耗额外的内存,并且没有线程问题,因为对象永远不会改变。

现在ThreadOne需要修改对象F。为了做到这一点,它只需创建一个新的F,一个包含它的新C和一个包含新C的新A。原始的B、D、E和G保持不变,不需要复制。

ThreadOne -> A2{ B1{ D1, E1 } C2{ F2, G1 } } 
ThreadTwo -> A1{ B1{ D1, E1 } C1{ F1, G1 } } 

这两个线程共享B、D、E和G的实例。

现在ThreadOne需要修改对象E。

ThreadOne -> A3{ B2{ D1, E2 } C2{ F2, G1 } } 
ThreadTwo -> A1{ B1{ D1, E1 } C1{ F1, G1 } } 

现在ThreadTwo需要最新版本,所以ThreadOne只需向其提供指向其副本的指针。
ThreadOne -> A3{ B2{ D1, E2 } C2{ F2, G1 } } 
ThreadTwo -> A3{ B2{ D1, E2 } C2{ F2, G1 } } 

由于这些对象是不可变的,所以不存在任何线程问题的风险。ThreadOne可以继续进行更改,每次仅创建已更改部分及其容器的新实例。

ThreadOne -> A4{ B3{ D2, E2 } C2{ F2, G1 } } 
ThreadTwo -> A3{ B2{ D1, E2 } C2{ F2, G1 } } 

ThreadOne -> A5{ B3{ D2, E2 } C3{ F3, G1 } } 
ThreadTwo -> A3{ B2{ D1, E2 } C2{ F2, G1 } } 

这是一个快速、内存高效且线程安全的方案。

1
哇,这实际上非常酷,我以前从未真正研究过不可变类。它们有明确的优点,我认为这可能确实是解决此问题的最佳方法!我唯一看到的缺点是每次子对象更改时都需要重新创建父对象的繁琐,但考虑到其他好处,我想我可以应对它。 - Ricket

1
你可以将命令模式和备忘录模式结合起来。 你可以拥有实现提交和回滚操作的具体命令,以及在提交之前保存修改对象状态的备忘录。

1
你所寻找的是命令模式。你可以创建命令对象,执行和撤销更改并仅存储一些基本状态,然后重播所需的任何命令以将对象置于所需状态。

不太对。我需要基础状态和最新状态都可以随机访问;我将有两个线程,一个使用基础状态,另一个使用最新状态。使用命令模式,我基本上会来回翻转对象(撤销和重做),这将非常低效,你肯定可以想象得到。 - Ricket
然后,您可以从基本状态回放到任何“随机”需要的状态,一旦回放到特定点,您就可以“重新基础”或“快照”所需运行的任何状态,并将其用作基础并从那里回放。这是执行您要查找的操作的“标准”模式。如果您尚未尝试实施它,则__不知道__它将如何执行。这就是所有日志系统的工作方式,没有魔术算法可供使用。这些命令基本上是与先前状态的增量。 - user177800

1

你所描述的听起来很像Memento模式。然而,你指定了不想存储对象的整个状态,所以该模式可能无法直接满足你的需求。

不过,你可以尝试修改它,使备忘录仅包含增量,而不是完整的对象状态。这将导致更小的内存占用。但这也意味着你只能按顺序回退状态,因为它们的增量会相互叠加。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接