重构大型“仅限状态”对象有哪些常用策略?
我正在研究一个特定的软实时决策支持系统,该系统可以对国家空域进行在线建模/模拟。该软件消耗大量实时数据馈送,并对空域中的大量实体的“状态”进行每分钟一次的估计。这个问题整齐地分解,直到我们达到目前最低级别的实体。
我们的数学模型估计/预测每个实体的过去和未来几个小时的时间线的50个参数,大约每分钟一次。目前,这些记录被编码为具有大量字段的单个Java类(一些字段被折叠为ArrayList
)。我们的模型正在不断发展,并且各个领域之间的依赖关系还没有一成不变,因此每个实例都会在一个复杂的模型中漫游,随着它的发展积累设置。
目前我们有以下内容,它使用构建器模式方法来构建记录的内容,并强制执行已知的依赖项(作为程序员错误的检查,随着模式的演变。)一旦估计是完成后,我们使用.build()
类型方法将以下内容转换为不可变形式。
final class OneMinuteEstimate {
enum EstimateState { INFANT, HEADER, INDEPENDENT, ... };
EstimateState state = EstimateState.INFANT;
// "header" stuff
DateTime estimatedAtTime = null;
DateTime stamp = null;
EntityId id = null;
// independent fields
int status1 = -1;
...
// dependent/complex fields...
... goes on for 40+ more fields...
void setHeaderFields(...)
{
if (!EstimateState.INFANT.equals(state)) {
throw new IllegalStateException("Must be in INFANT state to set header");
}
...
}
}
一旦完成了大量的这些估算,就会将它们汇总到时间轴中,分析汇总的模式/趋势。我们已经考虑过使用嵌入式数据库,但却遇到了性能问题。我们宁愿在数据建模方面对其进行整理,然后将部分软实时代码逐步移动到嵌入式数据存储中。
完成“时间敏感”部分后,产品将刷新为平面文件和数据库。
问题:
build()
方法非常麻烦。开箱即用的想法,但这是我们需要逐步发展的东西。在其他人说出来之前,我会注意到,如果该模型的数据表示很难得到,我们的数学模型可能会表明我们的数学模型不够清晰。公平的,我们正在努力,但我认为这是一个R& D环境的副作用,有很多贡献者,还有很多并发的假设在起作用。
(并不重要,但这是用Java实现的。我们使用HSQLDB或Postgres作为输出产品。我们不使用任何持久性框架,部分原因是缺乏熟悉性,部分原因是我们有足够的性能问题仅仅是数据库和手工编码的存储例程......我们对进一步的抽象持怀疑态度。)
答案 0 :(得分:5)
我遇到了很多同样的问题。
至少我认为我做过,听起来像我做的那样。表示方式不同,但在10000英尺处,声音几乎相同。离散的,“任意的”变量和它们之间的一系列临时关系(基本上是业务驱动的)的大量负载,可能会随时发生变化。
您还有另一个问题,您可以提及,这就是性能要求。听起来更快更好,可能一个缓慢完美的解决方案将被抛到快速糟糕的解决方案,只是因为较慢的一个无法满足基线性能要求,无论它有多好。
简单地说,我所做的就是为我的系统设计了一个简单的特定于域的规则语言。
DSL的全部内容是隐含地表达关系并将它们打包到模块中。
非常粗糙,人为的例子:
D = 7
C = A + B
B = A / 5
A = 10
RULE 1: IF (C < 10) ALERT "C is less than 10"
RULE 2: IF (C > 5) ALERT "C is greater than 5"
RULE 3: IF (D > 10) ALERT "D is greater than 10"
MODULE 1: RULE 1
MODULE 2: RULE 3
MODULE 3: RULE 1, RULE 2
首先,这不能代表我的语法。
但是你可以从模块中看到它是3个简单的规则。
关键是,由此可见,规则1取决于C,取决于A和B,B取决于A.这些关系是隐含的。
因此,对于该模块,所有这些依赖关系都“随之而来”。您可以看到我是否为模块1生成了类似的代码:
public void module_1() {
int a = 10;
int b = a / 5;
int c = a + b;
if (c < 10) {
alert("C is less than 10");
}
}
然而,如果我创建了第2单元,那么我得到的只是:
public void module_2() {
int d = 7;
if (d > 10) {
alert("D is greater than 10.");
}
}
在第3单元中,您会看到“免费”重用:
public void module_3() {
int a = 10;
int b = a / 5;
int c = a + b;
if (c < 10) {
alert("C is less than 10");
}
if (c > 5) {
alert("C is greater than 5");
}
}
所以,即使我有一个“汤”规则,模块根据依赖关系的基础,从而过滤掉它不关心的东西。抓住一个模块,摇动树木并保持悬挂的状态。
我的系统使用DSL生成源代码,但您也可以轻松地创建一个迷你运行时解释器。
简单的拓扑排序为我处理了依赖图。
所以,关于这一点的好处在于,尽管最终生成的逻辑中存在不可避免的重复,至少在模块之间,规则库中没有任何重复。您作为开发人员/知识工作者所维护的是规则库。
同样好的是你可以改变一个方程式,而不用担心副作用。例如,如果我改变做C = A / 2,那么,突然,B完全退出。但IF(C <10)的规则根本没有改变。
使用一些简单的工具,您可以显示整个依赖图,您可以找到孤立的变量(如B)等。
通过生成源代码,它将以您想要的速度运行。
就我而言,看到规则删除单个变量并看到500行源代码从结果模块中消失是很有趣的。这是500行,我不需要手动爬行并在维护和开发过程中删除。我所要做的就是在规则库中更改一条规则,让“魔法”发生。
我甚至可以做一些简单的窥视孔优化并消除变量。
这并不难。您的规则语言可以是XML,也可以是简单的表达式解析器。如果你不想,没有理由去Yacc或ANTLR。我会为S-Expressions插入一个插件,不需要语法,脑死解析。
实际上,电子表格也是一个很棒的输入工具。只是严格格式化。在SVN中合并很糟糕(所以,不要这样做),但最终用户喜欢它。您可能能够摆脱基于规则的实际系统。我的系统在运行时并不是动态的,并且不需要复杂的目标搜索和推理,因此我不需要这种系统的开销。但如果一个人开箱即用,那么快乐的一天。
哦,对于一个实现说明,对于那些不相信你可以在Java方法中达到64K代码限制的人,我可以向你保证可以做到:)。
答案 1 :(得分:3)
拆分大型数据对象与规范化大型关系表(第一和第二范式)非常相似。遵循规则至少达到第二范式,你可以对原始类进行良好的分解。
答案 2 :(得分:3)
答案 3 :(得分:1)
实际上,这似乎是游戏开发者面临的一个常见问题,由于深度继承树等原因,包含大量变量和方法的臃肿类。
有this blog post关于如何以及为什么选择合成而不是继承,也许它会有所帮助。
答案 4 :(得分:0)
您可以智能地分解大型数据类的一种方法是查看客户端类的访问模式。例如,如果一组类只访问字段1-20而另一组类只访问字段25-30,那么这些字段组可能属于不同的类。