巴林,我希望早点看到你的问题。因为这个技术基本上是我的“发明”(不管好坏),我可能能够提供帮助。
插入:我能给出的最简单的解释是,如果正常的执行就像将球抛起并接住它,那么差分执行就像是杂耍。
@windfinder的解释与我的不同,这没关系。这个技术不容易理解,我用了大约20年(断断续续)来找到可行的解释。让我在这里再试一次:
我们都理解计算机通过程序逐步执行,根据输入数据进行条件分支,并执行各种操作的简单思想。(假设我们只处理没有goto和return语句的简单结构化代码。)该代码包含一系列陈述,基本的结构化条件,简单循环和子程序调用。(现在暂时忘记有返回值的函数。)
现在想象两台计算机同时运行相同的代码,并能够互相比较。计算机1使用输入数据A运行,计算机2使用输入数据B运行。它们并排逐步运行。如果它们遇到条件语句,例如IF(test)....ENDIF,并且它们对于测试条件的真假意见不同,那么说测试条件为false的那台计算机将跳过ENDIF并等待另一台计算机追上。(这就是为什么代码结构化,因此我们知道另一台计算机最终会到达ENDIF。)
由于两台计算机可以相互通信,因此它们可以比较笔记,并详细说明两组输入数据和执行历史记录的不同之处。
当然,在差分执行(DE)中,只需使用一台计算机来模拟两台。
现在,假设你只有一个输入数据集,但想看看它从时间1到时间2发生了什么变化。假设你正在执行的程序是序列化器/反序列化器。当你执行时,你同时对当前数据进行序列化(写出)和反序列化(读取)以前的数据(上次执行时写入的)。现在,您可以轻松查看上次数据和本次数据之间的区别。
您正在写入的文件和从中读取的旧文件加起来构成队列或FIFO(先进先出),但这不是一个非常深刻的概念。
我在做一个图形项目时想到了这个问题,用户可以构建小型显示处理程序例程,称为“符号”,将它们组装成更大的例程以绘制像管道、储罐、阀门等图表之类的东西。我们希望这些图表是“动态”的,也就是说,它们可以增量更新自己,而不必重绘整个图表。(按今天的标准来看,硬件速度很慢)。我意识到(例如)绘制柱状图的例程可以记住其旧高度并进行增量式更新。
这听起来像面向对象编程,不是吗?然而,我可以利用图表过程执行序列的可预测性。我可以在顺序字节流中写入条形的高度。然后,为了更新图像,我可以以一种模式运行该过程,其中它会顺序读取其旧参数,同时写入新参数,以便准备下一次更新通行证。
这似乎非常显然,而且似乎只要例程包含条件语句,它就会失效,因为新流和旧流将不同步。但后来我意识到,如果还串行化了条件测试的布尔值,它们就可以重新同步。
需要遵循一个简单的规则(“擦除模式规则”),这将总是奏效。
结果是用户可以设计这些“动态符号”并将它们组装成更大的图表,而无论显示多么复杂或结构变化,都不必担心如何动态更新。
在那些日子里,我确实要担心视觉对象之间的干扰,以使擦除一个对象不会损坏其他对象。但现在我使用这种技术与Windows控件一起使用,并让Windows处理渲染问题。
它有什么用处?这意味着我可以通过编写一个过程来绘制控件来构建对话框,而且我不必担心实际记住控件对象或处理增量更新它们,或者根据条件要求使它们出现/消失/移动。结果是,对话框源代码要小得多,约为数量级,并且动态布局或更改控件数量或具有控件数组或网格是微不足道的。此外,例如,编辑字段可以轻松地绑定到它正在编辑的应用程序数据上,并且它始终是可证明正确的,而且我永远不必处理它的事件。为应用程序字符串变量添加编辑字段只需进行一次编辑即可。
对我来说最难解释的是需要以不同的方式思考软件。程序员们坚信着对象-操作的观点,他们想知道有哪些对象、有哪些类、如何“构建”显示界面以及如何处理事件,只有使用“樱桃炸弹”才能将他们赶出这种模式。我试图传达的是真正重要的是你需要说什么?假设你正在构建一个领域特定语言(DSL),你只需告诉它:“我想在这里编辑变量A,在那里编辑变量B,并在下面编辑变量C”,它就会自动为您处理。例如,在Win32中有一种用于定义对话框的“资源语言”。它是一个完美的DSL,但没有深入到足够程度。它不“生活在”主过程语言中,也不会为您处理事件,也不包含循环/条件/子例程。但它是好意,而Dynamic Dialogs试图完成这项工作。
所以,不同的思考方式是:编写程序时,首先找到(或发明)适当的DSL,并尽可能多地在其中编码程序。让它处理所有仅为实现而存在的对象和操作。
如果您想真正了解差分执行并使用它,则有一些棘手的问题可能会让您遇到困难。我曾经在Lisp宏中编写过它,其中这些棘手的部分可以为您处理,但在“普通”语言中,需要一些程序员纪律来避免陷阱。
抱歉表达得如此冗长。如果我说的不清楚,请指出来,我会尽力修正。
补充:
在Java Swing中,有一个示例程序称为TextInputDemo。它是一个静态对话框,占用270行(不包括50个州的列表)。在MFC中的Dynamic Dialogs中仅需约60行:
#define NSTATE (sizeof(states)/sizeof(states[0]))
CString sStreet;
CString sCity;
int iState;
CString sZip;
CString sWholeAddress;
void SetAddress(){
CString sTemp = states[iState];
int len = sTemp.GetLength();
sWholeAddress.Format("%s\r\n%s %s %s", sStreet, sCity, sTemp.Mid(len-3, 2), sZip);
}
void ClearAddress(){
sWholeAddress = sStreet = sCity = sZip = "";
}
void CDDDemoDlg::deContentsTextInputDemo(){
int gy0 = P(gy);
P(www = Width()*2/3);
deStartHorizontal();
deStatic(100, 20, "Street Address:");
deEdit(www - 100, 20, &sStreet);
deEndHorizontal(20);
deStartHorizontal();
deStatic(100, 20, "City:");
deEdit(www - 100, 20, &sCity);
deEndHorizontal(20);
deStartHorizontal();
deStatic(100, 20, "State:");
deStatic(www - 100 - 20 - 20, 20, states[iState]);
if (deButton(20, 20, "<")){
iState = (iState+NSTATE - 1) % NSTATE;
DD_THROW;
}
if (deButton(20, 20, ">")){
iState = (iState+NSTATE + 1) % NSTATE;
DD_THROW;
}
deEndHorizontal(20);
deStartHorizontal();
deStatic(100, 20, "Zip:");
deEdit(www - 100, 20, &sZip);
deEndHorizontal(20);
deStartHorizontal();
P(gx += 100);
if (deButton((www-100)/2, 20, "Set Address")){
SetAddress();
DD_THROW;
}
if (deButton((www-100)/2, 20, "Clear Address")){
ClearAddress();
DD_THROW;
}
deEndHorizontal(20);
P((gx = www, gy = gy0));
deStatic(P(Width() - gx), 20*5, (sWholeAddress != "" ? sWholeAddress : "No address set."));
}
新增内容:
以下是一个关于如何编辑医院病人数组的示例代码,共约40行代码。第1至6行定义了“数据库”。第10至23行定义了UI的总体内容。第30至48行定义了编辑单个患者记录所需的控件。请注意,该程序形式几乎不考虑时间上的事件,好像它所要做的只是创建一次显示。然后,如果添加或删除主题或进行其他结构性更改,它会被简单地重新执行,就好像它从头开始重新创建,除了DE会引起增量更新的发生。优点在于,作为程序员,您无需关注或编写任何代码来使UI的增量更新发生,并且它们是保证正确的。可能看起来这种重新执行会成为性能问题,但实际上并不是,因为更新不需要更改的控件大约需要数十纳秒的时间。
1 class Patient {public:
2 String name;
3 double age;
4 bool smoker;
5 };
6 vector< Patient* > patients;
10 void deContents(){ int i;
11
12 deLabel(200, 20, “Patient name, age, smoker:”);
13
14 FOR(i=0, i<patients.Count(), i++)
15 deEditOnePatient( P( patients[i] ) );
16 END
17
18 if (deButton(50, 20, “Add”)){
19
20 patients.Add(new Patient);
21 DD_THROW;
22 }
23 }
30 void deEditOnePatient(Patient* p){
31
32 int w = (Width()-50)/3;
33
34 deStartHorizontal();
35
36 if (deButton(50, 20, “Remove”)){
37 patients.Remove(p);
37 DD_THROW;
39 }
40
41 deEdit(w, 20, P(&p->name));
42 deEdit(w, 20, P(&p->age));
43
44 IF(p->age >= 50)
45 deCheckBox(w, 20, “Smoker?”, P(&p->smoker));
46 END
47 deEndHorizontal(20);
48 }
增加内容:Brian提出了一个好问题,我认为答案应该在这里的主要文本中:
@Mike:我不清楚“if (deButton(50, 20,“Add”)) {”语句实际上是做什么的。 deButton函数是什么?此外,您的FOR/END循环是否使用某种宏之类的东西? - Brian。
@Brian:是的,FOR/END和IF语句都是宏。 SourceForge项目有完整的实现。deButton维护一个按钮控件。当进行任何用户输入操作时,代码以“控件事件”模式运行,在该模式下,deButton检测到它被按下并通过返回TRUE表示它被按下。因此,“if(deButton(...)){...操作代码...}是一种将操作代码附加到按钮的方法,而无需创建闭包或编写事件处理程序。DD_THROW是一种在执行操作时终止传递的方式,因为操作可能已修改应用程序数据,因此继续通过例程的“控件事件”传递是无效的。如果将其与编写事件处理程序进行比较,它可以节省您编写此类代码,并让您拥有任意数量的控件。
添加:对不起,我应该解释一下我所说的“维护”这个词的含义。当首次执行该过程(在SHOW模式下)时,deButton创建一个按钮控件并在FIFO中记住其ID。在后续传递(在UPDATE模式下),deButton从FIFO获取ID,如有必要则对其进行修改,并将其放回FIFO。在ERASE模式下,它从FIFO中读取它,并销毁它,不会将其放回,从而“垃圾收集”它。因此,deButton调用管理控件的整个生命周期,使其与应用程序数据保持一致,这就是我说它“维护”它的原因。
第四种模式是EVENT(或CONTROL)。当用户键入字符或单击按钮时,该事件被捕获并记录,然后在EVENT模式下执行deContents过程。 deButton从FIFO获取其按钮控件的ID,并询问是否点击了该控件。如果是,则返回TRUE,以便可以执行操作代码。如果没有,则只返回FALSE。另一方面,deEdit(...,&myStringVar)
检测事件是否针对它,并将其传递给编辑控件,然后将编辑控件的内容复制到myStringVar中。在此和正常的UPDATE处理之间,myStringVar始终等于编辑控件的内容。就是这样实现“绑定”的。相同的思想适用于滚动条,列表框,组合框,任何一种允许您编辑应用程序数据的控件。
这是我在维基百科上的编辑链接:http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article