用于随时间变化的事物的模式
A pattern is an idea that has been useful in one practical context and will probably be useful in others
出处: http://martinfowler.com/ap2/timeNarrative.html
建议:读者最好先读一下关于分析模式的概念,这在Martin Fowler的网站上有介绍,国内一些网站也有其前言的译文。
总结不同模式你可以回答过去信息的状态,这些信息包括从“Martin 于1991年7月1日的住址?”到“我们在1999年8月12日寄给Martin的帐单认为他1999年7月1日的地址是什么?”
事物总在变化,如果我们保存了这个世界的信息这也许就不是问题了,毕竟当事物发生变化时计算机信息系统能让我们容易的改变一个记录而无须求助于的连续的纸张上或重新一页页打印这些信息。
当我们需要记下事物的变化的历史记录时,事情就变的有趣了,我们不但要知道世界现在的情况,也要知道6个月之前的情况,更糟的是我们可能还想知道6个月前的再2个月前的情况,这些问题把我们领入了精彩的临时模式,它让我们通过组织一些对象来让我们容易的解决问题,而不会完全弄乱我们的业务模型,在对象化所带来的挑战中,这种模式是最常见和复杂的一种。
最简单的解决办法是用一个Audit Log ..(计帐日志),这里你可以关心记录的变化,但你并不期望经常回去使用它,然而你想它很容易建立并且能最小化的影响你的工作,当有人想看看以前的信息时,那你就希望它能为找出这些信息做许多工作,如果他们不是经常使用它,而且不需要立刻得到结果,那没什么问题,实际上如果你使用数据库,就不费什么事了。
当信息的可访问性更强时,你就必须多花些工夫了,使用此最常见的场合是Effectivity(有效模式)有效性,这通常给一个对象一个有效时段,那么当你使用时段处理你的对象时就可以得到正确时间短内的正确对象。
Figure 1: Using an explicit date range to show effectivity.
用Effectivity 的问题在于它是明显和直接的,所有在上下文环境下的使用者必须知道“临时”的方面,这会使模型复杂化,所以如果临时性问题变的深入时,那就有必要在不需要时隐藏他们,并且使在用他们时更方便。
如果你只有几个对象的属性需要访问临时信息,那就可以使用Temporal Property (临时属性),其要点是去掉有效期,而是用有规则的属性,但是要一个带有时间参数的访问方法,这样就能允许你提诸如“这个属性在2000年4月1日是什么值?”的问题。
Figure 2: Using a temporal property for the address..
图2:为地址使用临时的属性
Temporal Property(临时属性)的要点是对属性有一个带有时间参数的访问方法而不是使用一大串用Effectivity的对象,当然这两种方式并不矛盾,你可以让使用address的对象实现Temporal Property的接口,如果这种对象带有其他的职责,你可以为那些需要那些服务用户提供访问地址的方法,你也可以给那些认为Temporal Property方便的人提供接口。
Temporal Property引入了一种将事物与基于时间的索引联系起来的观念,但限制是在单个属性,如果你在对象中有很多属性有临时性的特点,这种方式就显得笨拙了,对此,有两种解决方式:第一种是使用Snapshot,它可以给你一个引用处在某时间点上实际对象状态的对象,那样如果你想查一下顾客在2000年4月1日的一些情况,那就请求一个顾客在那个时间的一个快照对象然后再访问各个属性的值,而不必重复输入日期。
图3:一个显示对象在某时间的快照对象
Snapshot允许你有一个在某一时间对象的视图,但有由于随着时间变化不断发生,你实际上关心一个对象的经历各个版本,这就引入了Temporal Object,临时对象有很多方式,但最直接的是如图4的两个类,因为contract 类代表了贯穿前后时间的合同,通常被其它类所引用,它包含了记录了合同在不同时间状态各个版本的集合,这允许人们查阅合同的一些不变的信息,也可以查阅在某时间点上特定版本。
Figure 4: Temporal Objects have an explicit history of versions, so that each change leads to a new version.
在实际中我注意到Effectivity 广泛应用,但通常是因为人们对Temporal Property和其他较复杂的模式不够熟悉,象Temporal Property 和Temporal Object 的模式由于他们隐藏了许多临时行为的机制所以工作得很好,不过当人们需要一个只在特定时间段内有效的对象的情况下,Effectivity还是很重要
时间的维
正如我在上面所描述的,时间问题在建摸上的确是一个有挑战性的问题,正如一些差劲的科幻书籍提到,时间是四维的,糟糕的是,那是错误的。
我发现描述这个问题的最好的方法是例子,想象以下我们有一个工资系统,它知道一个员工从1月1日有$100/天的等级,在2月25日我们用这个比率运行了工资系统,在3月15日我们知道,有效期2月15日截止,该员工的等级变到了$211/天,我们如何回答在25日这天的比率是多少呢?
直觉上我们应该回答$211,因为是那个数字,但通常我们不能忽视在2月25日我们认为比率是$100,因为我们运行系统的时候是$100,我们打印了支票,他取了钱,如果税务监督部门来问他在2月25日的比率,这就变的重要了。
实际上可以认为工资水平有两种记载对我们很有用,我们现在知道的,以及在2月25日所知道的。实际大体上我们可以说不仅存在dinsdale过去的工资水平记载,还有一个dinsdale的工资水平的记载的记载,但是有一个工资水平的记载,时间不是四维了,而是四维了或五维了。
我认为时间的第一维是实际时间:事情发生的时间,第二维是记录时间,我们知道它发生的时间,无论何时事情发生,总有两件事情会随即发生,Dinsdale的工资在2月15上升,记录的改变在3月15日,同样,如果我们问Dinsdale的工资是多少,我们实际需要提供两种日期:记录时间和实际时间。
记录时间
实际时间
Dinsdale的工资
Jan 1
Jan 1
$100/day
Feb 15
Feb 15
$100/day
Feb 25
Feb 15
$100/day
Feb 25
Feb 25
$100/day
Mar 14
Feb 15
$100/day
Mar 15
Jan 1
$100/day
Mar 15
Feb 15
$211/day
Mar 15
Feb 25
$211/day
我们可以象这样考虑两维,实际的记载回顾实际的时间,如果看看我目前的实际记载,那么就会发现Dinsdale的工资在2月15日从 $100上升到了$211,当然那是现在的实际的记录,如果2月25日的实际记录,Dinsdale 从1月1日工资为$100,从未到达过$211,每一天(严格的说,每一时间点)在记录时间上有一个实际的历史记载,正如我们曾经认为正确的事情不再正确那样,那些记载也不相同。
从另外一个角度,我们可以说在实际记载中的每一天有一个记录历史。那些记录历史告诉我们,我们的认知怎样随时间变化,所以在2月25日这个实际时间有一个历史记载即一直到3月15日,Dinsdale的工资是$100,在那一天的时间点上增加到了$211。
让我们更深一步,假设我们在3月26日作了相应的调整,在4月4日我们被告知先前的信息有误,比率实际上在2月15日增加到$255,现在,我们如何回答“在2月25日Dinsdale的工资是多少”这个问题?
我知道成熟的开发者在面对这样的问题是头昏脑胀,但是一旦你发现任何事物以这种两维的观点看问题,事情就变得简单多了,以下用一张扩展的表格来表述。
记录时间
实际时间
雇员工资
Jan 1
Jan 1
$100/day
Feb 15
Feb 15
$100/day
Feb 25
Feb 15
$100/day
Feb 25
Feb 25
$100/day
Mar 14
Feb 25
$100/day
Mar 15
Jan 1
$100/day
Mar 15
Feb 15
$211/day
Mar 15
Feb 25
$211/day
Mar 26
Feb 25
$211/day
Apr 4
Jan 1
$100/day
Apr 4
Feb 14
$100/day
Apr 4
Feb 15
$255/day
Apr 4
Feb 25
$255/day
如果我们看一下目前的实际记载(今天的实际记载)那么我们就会说Dinsdale的工资从1月1日为$100,到2月15日上升到$255,对目前的记载来看$211并没有发生,因为它从来就不对,如果我们看一下3月26日的记载,我们会说Dinsdale在2月15日升到$211,在这里,$255从未发生,因为那时我们还不知道。
我们也可以考虑2月25日的记载,现在这个记载说比率为100(当天)知道3月15日变为了$211,在4月4日又变化到$255.
一旦你了解了二维性,考虑这样的问题就变的容易,但是一想到实现还是会惊慌,幸运的是有几样东西会是你的实现变的容易。
第一个简化就是使用Audit Log(日志模式)来应对这些变化,所有你要做的只是在日志的每一个条目中同时计下记录时间和实际时间,这个简单的练习对二维有足够的功效,而且我认为如果你被其中一个所烦心,这样做是值得的。
第二种简化是你并不是经常希望你的模型去处理这两种维,在这里重要的是你的模型有哪些,还有就是哪些要放入日志。
如果我们想保存事物的历史来知道它是怎样随时间变化,但是并不关心何时得知了这种变化,我们可以称那是actual-temporal.所以如果我保留一个雇员的地址也许会选择actual-temporal属性,对于在线查询信息系统而言它能工作的很好,因为当你访问数据库时你通常想知道实际的历史。
当你有一个象基于对象状态产生票据的系统,Record-temporal通常在这种情况下出现,这种情况会引入票据如何计算的问题,这使你需要了解当票据计算时所认为的对象的状态是什么,Record-temporal可以经常与一个版本控制软件系统,在哪里你可以回到过去并且问“这个文件在4月1日是怎样的?”
当然也有同时需要这两种信息的时候- bi-temporal facts,本质上它同时需要两种时间(记录时间和实际时间)。
Bi-temporality是一个完全的解决方案,但是总是值得思考围绕它的方式。比如帐单计算,如果你想找出为什么帐单要那样计算,有一种可能就是有一个bi-temporal 的数据库。然而,当你计算帐单时,保存一个详细的计算过程,样就比bi-temporal简化很多,也满足了需要。
修改一个临时记录
到现在为止,我只是讨论了在访问信息的方面,没有讨论到修改,你打算怎样修改会导致更多的决定。但是其中的许多包含了有用的简化。
总而言之,修改时间信息是相当混乱的,如果我们有一个雇员的工资水平从2月15日到4月15日是$211,一个通常的完全修改会允许改变所有开始日期,结束日期,值的组合,为此提供一个接口会很难用,因为它要求客户知道临时信息如何工作。
第一种简化是你可能只有附加的变化,一个新增的变化总是加到记录的末尾, 一个例子是“使雇员的2月15日的$211的比率有效”,虽然最初看上去好象你只是从混合中移去了一块数据,但结果是大大简化了修改,在实际中绝大多数的修改是新增,所以这样可以使客户容易使用,进一步,对附加的修改的任意组合做任意修改,然而当对象有复杂的历史时,这样会有可怕的混乱,这种特性允许你通过只是提供一个附加的接口简化有简单历史的对象。
第二种简化是允许当前的修改,一个当前的修改在一个当前有效的日期允许记录的变化,一般而言,即使额外的变化可以在过去和将来可能发生,一个当前的修改完全不需要日期信息,允许你有一个非临时性修改的接口。
当前的修改看起来太好而不实际,如果只是修改当前的变化,为什么要有临时信息呢?这里有用的消息临时记录系统只能进行当前的修改,不仅对一个临时信息的记录有追溯效力的修改破坏了记录的完整性,也没有不能被实际时间这种维处理这种需求(除非你的需求之一是欺诈)。这是一个很大的进步,因为它意味着整个维有无形的变化,-你只是需要在查询的时候担心记录时间,而不是修改。
本人还会翻译与与此篇相关的文章: 临时属性,临时对象等,这样读者会对此有一个更好的理解。
List of Patterns
Audit Log 日志模式
一个简单的变化日志,目的是容易编写并且不影响系统
Effectivity 有效模式
给一个对象添加一个时间段来显示它何时有效。
Snapshot 快照模式
在某个时间点上一个对象的视图。
Temporal Object 临时对象
一个随时间变化的对象
Temporal Property 临时属性
随时间变化的属性
Time Point 时间点
使用粒度表达一个时间点
本人还会翻译与与此篇相关的文章: 临时属性,临时对象等,这样读者会对此有一个更好的理解。