e,布莱恩,我希望我早日看到您的问题。由于这几乎是我的“发明”(无论好坏),我也许可以提供帮助。
插入:我可以做的最简短的解释是,如果正常执行就像将球扔向空中并接住球,那么差异执行就像玩杂耍。
@windfinder的解释与我的不同,没关系。这项技术很难缠住别人的头,我花了20年的时间(反复)才能找到可行的解释。让我再给它一个镜头:
我们都了解计算机逐步执行程序,根据输入数据进行条件分支并执行操作的简单想法。(假设我们只处理简单的结构化goto-less,less-return代码。)该代码包含语句序列,基本结构化条件,简单循环和子例程调用。(现在就不用考虑返回值的函数了。)
现在,假设两台计算机彼此同步执行同一代码,并且能够比较注释。计算机1使用输入数据A运行,计算机2使用输入数据B运行。它们并排运行。如果他们遇到诸如IF(test).... ENDIF之类的条件语句,并且如果对测试是否为真有不同意见,那么说测试是否为false的人将跳到ENDIF并等待它的妹妹要赶上。(这就是代码结构化的原因,因此我们知道姐妹最终将进入ENDIF。)
由于两台计算机可以相互交谈,因此它们可以比较注释并详细说明两组输入数据和执行历史记录之间的区别。
当然,在差分执行(DE)中,它是由一台计算机完成的,模拟两台计算机。
现在,假设您只有一组输入数据,但是您想查看它在时间1到时间2之间的变化。假设您正在执行的程序是串行器/解串器。在执行时,您既要序列化(写出)当前数据,又要反序列化(读入)过去的数据(上次执行时写入的数据)。现在,您可以轻松查看上一次数据与这次数据之间的区别。
您要写入的文件和要读取的旧文件一起构成一个队列或FIFO(先进先出),但这不是一个很深的概念。
这是我在图形项目中工作时发生的,用户可以在其中构建很少的显示处理器例程,即“符号”,这些例程可以组装成较大的例程,以绘制诸如管道,储罐,阀门等之类的东西。我们希望这些图是“动态的”,因为它们可以不断更新自己而不必重绘整个图。(按照今天的标准,硬件速度很慢。)我意识到(例如)绘制条形图条形图的例程可以记住它的旧高度,并且可以进行增量更新。
这听起来像是OOP,不是吗?但是,我可以利用图过程的执行顺序的可预测性,而不是“使”成为“对象”。我可以在一个连续的字节流中写出小节的高度。然后,要更新映像,我可以以一种模式运行该过程,在该模式下,它依次读取其旧参数,同时写入新参数,以便为下一次更新做好准备。
这看起来非常愚蠢,并且似乎在过程包含条件条件后立即中断,因为这样新的流和旧的流将不同步。但是后来我想到,如果他们还对条件测试的布尔值进行序列化,他们可以重新同步。花了一段时间说服自己,然后证明这会一直工作,提供了一个简单的规则(以下简称“擦除模式规则”)之后。
最终结果是,用户可以设计这些“动态符号”并将它们组装成更大的图表,而不必担心它们将如何动态更新,而不管显示器的复杂程度或结构可变性如何。
在那些日子里,我确实不得不担心视觉对象之间的干扰,这样擦除一个对象就不会损坏其他对象。但是,现在我将这种技术与Windows控件一起使用,并且让Windows处理渲染问题。
那么它能实现什么呢?这意味着我可以通过编写一个绘制控件的过程来建立一个对话框,而我不必担心实际记住控件对象或逐步更新它们,或者在条件允许的情况下使其出现/消失/移动。结果是对话框源代码更小,更简单,大约减少了一个数量级,并且诸如动态布局或更改控件的数量或具有控件的数组或网格之类的事情微不足道。另外,诸如Edit字段之类的控件可以被微不足道地绑定到它正在编辑的应用程序数据上,并且它将始终被证明是正确的,而且我不必处理它的事件。放置应用程序字符串变量的编辑字段是单行编辑。
我发现最难解释的是,它需要对软件进行不同的思考。程序员如此坚定地与软件的对象操作视图结合在一起,以至于他们想知道什么是对象,什么是类,如何“构建”显示以及如何处理事件,这花了很多时间。炸弹将他们炸开。我试图传达的是真正重要的是您需要说什么?想象一下,您正在构建一种特定于域的语言(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 >= 50
5 };
6 vector< Patient* > patients;
10 void deContents(){ int i;
11 // First, have a label
12 deLabel(200, 20, “Patient name, age, smoker:”);
13 // For each patient, have a row of controls
14 FOR(i=0, i<patients.Count(), i++)
15 deEditOnePatient( P( patients[i] ) );
16 END
17 // Have a button to add a patient
18 if (deButton(50, 20, “Add”)){
19 // When the button is clicked add the patient
20 patients.Add(new Patient);
21 DD_THROW;
22 }
23 }
30 void deEditOnePatient(Patient* p){
31 // Determine field widths
32 int w = (Width()-50)/3;
33 // Controls are laid out horizontally
34 deStartHorizontal();
35 // Have a button to remove this patient
36 if (deButton(50, 20, “Remove”)){
37 patients.Remove(p);
37 DD_THROW;
39 }
40 // Edit fields for name and age
41 deEdit(w, 20, P(&p->name));
42 deEdit(w, 20, P(&p->age));
43 // If age >= 50 have a checkbox for smoker boolean
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:是的,FOR / END和IF语句是宏。SourceForge项目具有完整的实现。deButton维护按钮控件。当发生任何用户输入操作时,代码将在“控制事件”模式下运行,在该模式下,deButton检测到已按下该代码,并通过返回TRUE表示已按下它。因此,“ if(deButton(...)){...操作代码...}是一种将操作代码附加到按钮的方法,而无需创建闭包或编写事件处理程序。DD_THROW是一个执行操作时终止传递的方法,因为该操作可能已修改了应用程序数据,因此继续执行“控制事件”传递例程是无效的。如果将此与编写事件处理程序进行比较,可以节省编写代码的过程,它可以让您拥有任意数量的控件。
补充:对不起,我应该解释“维护”一词的含义。首次执行该过程时(在SHOW模式下),deButton创建一个按钮控件,并在FIFO中记住其ID。在随后的遍历中(在UPDATE模式下),deButton从FIFO获取ID,并在必要时对其进行修改,然后将其放回FIFO。在ERASE模式下,它从FIFO中读取,销毁它并且不将其放回去,从而“垃圾回收”它。因此,deButton调用可管理控件的整个生命周期,使其与应用程序数据保持一致,这就是为什么我说它“保持”了它的原因。
第四种模式是事件(或控制)。当用户键入字符或单击按钮时,将捕获并记录该事件,然后在事件模式下执行deContents过程。deButton从FIFO获取其按钮控件的ID,并询问是否单击了该按钮。如果是,则返回TRUE,因此可以执行操作代码。如果不是,则仅返回FALSE。另一方面,deEdit(..., &myStringVar)
检测该事件是否是针对该事件的,如果是,则将其传递给编辑控件,然后将编辑控件的内容复制到myStringVar。在此过程与正常的UPDATE处理之间,myStringVar始终等于编辑控件的内容。这就是完成“绑定”的方式。相同的想法适用于滚动条,列表框,组合框以及任何允许您编辑应用程序数据的控件。
这是我的维基百科编辑的链接:http : //en.wikipedia.org/wiki/User : MikeDunlavey/Difex_Article