面向数据编程 Data-Oriented Programming [21]

本章探讨了如何通过不可变数据和多版本状态管理来确保系统数据完整性。计算阶段使用不可变函数处理数据,提交阶段则集中验证和更新状态。提交时,系统状态的前一个版本被保存,以便于撤销操作。通过结构共享,系统可以高效地跟踪和恢复历史状态。撤销机制简单地使当前状态引用回退到前一版本。这种做法类似于Git的提交钩子,允许轻松撤销和验证系统状态。
摘要由CSDN通过智能技术生成

4.6 确保系统状态完整性

你:函数在计算阶段处理不变数据的方式仍然困扰着我:我们如何保持数据完整性?

乔:你是什么意思?

你:在面向对象中,数据只由与数据属于同一类的方法操作。它可以防止其他类损坏类的内部状态。

乔:你能给我举一个图书馆失效的例子吗?

你:例如,假设突变的代码将一个图书项目添加到成员的图书借阅中,而没有在catalog中将该图书项目标记为已借出。那么系统数据就会被破坏。

乔:在DO中,我们有权确保整个系统级别的数据完整性,而不是将验证分散在许多类中。

你:我不明白。

乔:提交阶段的代码对于所有变化都是通用的,这一事实允许我们在中心位置验证系统数据:在提交阶段的开始,有一个步骤检查(请参见清单4.8)要提交的系统状态的版本是否有效。如果数据无效,提交将被拒绝。

清单4.8 提交阶段内的数据验证

SystemState.commit = function(previous, next) {
	if (!SystemValidity.validate(previous, next) {
			throw "The system data to be committed is not valid!";
		});
	this.systemData = next;
}

你:听起来很像git中的提交Hook。

乔:我喜欢你的比喻!

你:为什么要将前一个传递给SystemValidity.valify(),而不是传递给下一个呢?

乔:因为它允许SystemValidity.valify()的代码在计算方面优化验证。例如,我们可以只验证已更改的数据部分。

TIP 在DO中,我们将系统数据作为一个整体进行验证。数据验证与数据操作分离。

你:SystemValidity.valify()的代码是什么样子的?

乔:我会在第二部分向你展示,例如,我们如何确保图书记录中提到的每个作者的身份都是有效的。它涉及更高级的数据操作逻辑。

4.7 时间旅行

  通过结构共享操作不变数据的多版本状态方法的另一个优点是,我们可以跟踪数据的所有版本的历史记录,而不会破坏程序的内存。例如,它允许我们非常容易地将系统恢复到较早的状态。

你:你之前告诉我,把系统恢复到以前的状态很容易。你能教我怎么做吗?

乔:很乐意。但在此之前,我想确保您理解为什么跟踪所有版本的数据在内存方面是高效的。

你:我认为这与不可变函数使用结构共享的事实有关。并且该状态的后续版本之间的大部分数据是共享的。

TIP 结构共享允许我们保留系统状态的多个版本,而不会导致内存爆炸。

乔:太好了。现在,我将向你们展示撤销突变是多么简单。为了实现撤消,我们的SystemState类需要有两个对系统数据的引用:对系统当前状态的systemData引用和对系统以前状态的previousSystemData引用。

你:这是有道理的。

乔:在提交阶段,我们更新previousSystemData和systemData。

你:要实现撤销需要做些什么呢?

乔:Undo是通过让systemData引用与previousSystemData相同版本的系统数据来实现的。

你:你能给我举个例子吗?

乔:为了简单起见,我会给每个版本的系统状态一个数字。它从V0开始,每次提交突变时,版本都会递增:V1、V2、V3等…

你:好的。

乔:假设目前我们的系统状态是V12(参见图4.6)。在SystemState对象中,systemData指的是V12,previousSystemData指的是V11。

图4.6当系统状态为V12时,systemData指V12,previousSystemData指V11

 

你:到目前为止还不错。

乔:现在,当提交一个突变(例如,添加一个成员)时,两个引用都向前移动:systemData指的是V13,previousSystemData指的是V12

图4.7提交突变时,systemData指的是V13,previousSystemData指的是V12

 

你:我想,当我们撤销突变时,两个引用都会向后移动。

乔:理论上是这样的。但在实践中,它需要维护所有状态引用的堆栈。目前,为了简化操作,我们只保留对前一个版本的引用。因此,当我们撤销突变时,两个引用都引用了V12,如图4.8所示。

图4.8当突变被撤消时,systemData和previousSystemData都是指V12

 

你:你能告诉我如何实现这个撤销机制吗?

乔:实际上,只需要对SystemState类做几处更改。结果如清单4.9所示。请注意Commit()函数中的更改:我们在systemDataBeforeUpdate中保留了对系统当前状态的引用。如果验证和冲突解决成功,我们将同时更新previousSystemData和systemData。

清单4.9 具有撤消功能的SystemState类

class SystemData {
	systemData;
	previousSystemData;

	get() {
		return this.systemData;
	}

	commit(previous, next) {
		var systemDataBeforeUpdate = this.systemData;
		if (!Consistency.validate(previous, next) {
				throw "The system data to be committed is not valid!";
			});
		this.systemData = next;
		this.previousSystemData = systemDataBeforeUpdate;
	}

	undoLastMutation() {
		this.systemData = this.previousSystemData;
	}
}

你:我看到实现System.undoLastMutation()仅仅是让systemData引用与previousSystemData相同的值。

乔:就像我告诉你的,如果我们需要允许多次撤销,代码会更复杂一些。但你明白我的意思了。

4.8 总结

  在本章中,我们探索了DO如何通过多版本方法管理状态,在多版本方法中,变异被分成计算和提交两个阶段。
  在计算阶段,使用不可变的函数操作数据,这些函数利用结构共享来高效地(内存和计算)创建新版本的数据,其中共享两个版本之间通用的数据,而不是复制。
  接下来,状态引用发生在提交阶段,这是我们系统中唯一有状态的部分。提交阶段的代码对所有突变都是通用的,这一事实允许我们在更新状态之前在中心位置验证系统状态。
  此外,保存系统数据版本的历史记录也很容易和高效,并且可以直接将系统恢复到以前的状态。作为一个例子,我们已经了解了如何在一个撤消系统中实现撤消。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值