我希望我早点看到你的问题。因为它几乎是我的“发明”(无论好坏),我或许可以提供帮助。
插入:我可以做的最简单的解释是,如果正常执行就像在空中投球并抓住它,那么差异执行就像玩杂耍一样。
@ windfinder的解释与我的不同,那没关系。这种技术并不容易缠绕在一起,而且花了大约20年的时间(关闭和开启)才能找到有效的解释。让我再说一遍:
我们都理解计算机单步执行程序,根据输入数据获取条件分支以及执行操作的简单概念。(假设我们只处理简单的结构化goto-less,无返回代码。)该代码包含语句序列,基本结构化条件,简单循环和子例程调用。(忘记现在返回值的函数。)
现在想象两台计算机彼此锁定执行相同的代码,并能够比较笔记。计算机1运行输入数据A,计算机2运行输入数据B.它们一步一步地并行运行。如果他们来到像IF(测试)...... ENDIF这样的条件语句,并且如果他们对测试是否为真有不同意见,那么说测试的人如果错误跳到ENDIF并等待它的姐姐赶上了。(这就是代码结构化的原因,所以我们知道姐姐最终会到达ENDIF。)
由于两台计算机可以相互通信,因此它们可以比较注释并详细说明两组输入数据和执行历史是如何不同的。
当然,在差分执行(DE)中,它是用一台计算机完成的,模拟两台计算机。
现在,假设您只有一组输入数据,但是您想看看它从时间1到时间2的变化情况。假设您正在执行的程序是序列化器/解串器。在执行时,您既可以序列化(写出)当前数据,也可以反序列化(读入)过去的数据(上次执行此操作时写入)。现在,您可以轻松查看上次数据与此次数据之间的差异。
您正在写入的文件和您正在读取的旧文件一起构成一个队列或FIFO(先进先出),但这不是一个非常深刻的概念。
当我在一个图形项目上工作时,我想到了这一点,用户可以构建一个名为“符号”的小型显示处理器例程,这些例程可以组装成更大的例程来绘制管道,坦克,阀门等图形。我们希望图表是“动态的”,因为他们可以逐步更新自己而无需重绘整个图表。(按照今天的标准,硬件很慢。)我意识到(例如)绘制条形图条的例程可以记住它的旧高度并且只是逐步更新自身。
这听起来像OOP,不是吗?但是,我可以利用图表过程的执行顺序的可预测性,而不是“制造”一个“对象”。我可以在一个连续的字节流中写出条形高度。然后,为了更新图像,我可以在一种模式下运行该过程,在该模式下,它在写入新参数时顺序读取其旧参数,以便为下一次更新传递做好准备。
这似乎是非常明显的,并且一旦过程包含条件,似乎就会中断,因为那时新流和旧流将不同步。但后来我突然意识到,如果他们也序列化条件测试的布尔值,他们可以恢复同步。花了一段时间才说服自己,然后证明,如果遵循一个简单的规则(“擦除模式规则”),这将始终有效。
最终结果是用户可以设计这些“动态符号”并将它们组装成更大的图表,而无需担心它们如何动态更新,无论显示器的复杂性或结构变化如何。
在那些日子里,我确实不得不担心视觉对象之间的干扰,因此擦除它不会损害其他对象。但是,现在我使用Windows控件的技术,我让Windows负责渲染问题。
那么它实现了什么?这意味着我可以通过编写一个绘制控件的过程来构建一个对话框,我不必担心实际记住控件对象或处理逐步更新它们,或者让它们在条件允许时出现/消失/移动。结果是更小和更简单的对话框源代码,大约一个数量级,并且诸如动态布局或改变控件的数量或具有控件的阵列或网格之类的事情是微不足道的。此外,诸如编辑字段之类的控件可以简单地绑定到它正在编辑的应用程序数据,并且它总是可证明是正确的,并且我永远不必处理它的事件。将应用程序字符串变量放在编辑字段中是一行编辑。
我发现最难解释的是,它需要对软件进行不同的思考。程序员如此坚定地坚持软件的对象 - 动作视图,他们想知道什么是对象,什么是类,他们如何“构建”显示,以及他们如何处理事件,它需要一个樱桃炸弹炸出它们。我试图传达的是,真正重要的是你需要说什么?想象一下,你正在构建一个特定于域的语言(DSL),你需要做的就是告诉它“我想在这里编辑变量A,变量B在那里,变量C在那里”,它会神奇地为你处理它。例如,在Win32中有用于定义对话框的“资源语言”。它是一个非常好的DSL,除了它还不够远。它不会“存在”主要的过程语言,或为您处理事件,或包含循环/条件/子例程。但这意味着很好,动态对话试图完成这项工作。
因此,不同的思维模式是:编写程序,首先找到(或发明)适当的DSL,并尽可能多地编写程序代码。让它处理所有的对象和动作只存在于执行的缘故。
如果你想真正了解差异执行并使用它,那么有一些棘手的问题可能会让你失望。我曾经用Lisp宏编写它,可以为你处理这些棘手的位,但在“普通”语言中,它需要一些程序员纪律来避免陷阱。
很抱歉这么啰嗦。如果我没有意义,如果你指出它并且我可以尝试修复它,我会很感激。
添加:
在Java Swing中,有一个名为TextInputDemo的示例程序。它是一个静态对话框,占用270行(不包括50个状态列表)。在动态对话框中(在MFC中)它大约有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; // smoker only relevant if age >= 505 };6 vector< Patient* > patients;10 void deContents(){ int i;11 // First, have a label12 deLabel(200, 20, “Patient name, age, smoker:”);13 // For each patient, have a row of controls14 FOR(i=0, i<patients.Count(), i++)15 deEditOnePatient( P( patients[i] ) );16 END17 // Have a button to add a patient18 if (deButton(50, 20, “Add”)){19 // When the button is clicked add the patient20 patients.Add(new Patient);21 DD_THROW;22 }23 }30 void deEditOnePatient(Patient* p){31 // Determine field widths32 int w = (Width()-50)/3;33 // Controls are laid out horizontally34 deStartHorizontal();35 // Have a button to remove this patient36 if (deButton(50, 20, “Remove”)){37 patients.Remove(p);37 DD_THROW;39 }40 // Edit fields for name and age41 deEdit(w, 20, P(&p->name));42 deEdit(w, 20, P(&p->age));43 // If age >= 50 have a checkbox for smoker boolean44 IF(p->age >= 50)45 deCheckBox(w, 20, “Smoker?”, P(&p->smoker));46 END47 deEndHorizontal(20);48 }
补充:Brian问了一个很好的问题,我认为答案属于正文:
@Mike:我不清楚“if(deButton(50,20,”Add“)){”声明实际上在做什么。deButton函数有什么作用?你的FOR / END循环是使用某种宏还是什么? - 布莱恩
@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处理和正常UPDATE处理之间,myStringVar始终等于编辑控件的内容。这就是“绑定”的方式。同样的想法适用于滚动条,列表框,组合框,任何允许您编辑应用程序数据的控件。
这是我维基百科编辑的链接:http://en.wikipedia.org/wiki/User : MikeDunlavey/Difex_Article