Gee, Brian, ich wünschte ich hätte deine Frage früher gesehen. Da es so ziemlich meine "Erfindung" ist (zum Guten oder Schlechten), kann ich vielleicht helfen.
Eingefügt: Die kürzest mögliche Erklärung, die ich machen kann, ist, dass wenn die normale Ausführung wie das Werfen eines Balls in die Luft und das Fangen ist, die differenzielle Ausführung dem Jonglieren gleicht.
Die Erklärung von @ windfinder unterscheidet sich von meiner, und das ist in Ordnung. Diese Technik ist nicht einfach, den Kopf herumzureißen, und ich habe ungefähr 20 Jahre (ab und zu) gebraucht, um Erklärungen zu finden, die funktionieren. Lassen Sie es mich hier noch einmal versuchen:
Wir alle verstehen die einfache Idee, dass ein Computer ein Programm durchläuft, bedingte Verzweigungen basierend auf den Eingabedaten nimmt und Dinge tut. (Angenommen, es handelt sich nur um einfachen strukturierten Code ohne Rückgabe und ohne Rückgabe.) Dieser Code enthält Folgen von Anweisungen, strukturierte Grundbedingungen, einfache Schleifen und Unterprogrammaufrufe. (Vergessen Sie vorerst Funktionen, die Werte zurückgeben.)
Stellen Sie sich nun zwei Computer vor, die denselben Code im Gleichschritt miteinander ausführen und Notizen vergleichen können. Computer 1 wird mit Eingabedaten A und Computer 2 mit Eingabedaten B ausgeführt. Sie werden Schritt für Schritt nebeneinander ausgeführt. Wenn sie zu einer bedingten Aussage wie IF (Test) .... ENDIF kommen und wenn sie unterschiedliche Meinungen darüber haben, ob der Test wahr ist, dann springt derjenige, der den Test sagt, wenn er falsch ist, zum ENDIF und wartet herum seine Schwester aufzuholen. (Aus diesem Grund ist der Code strukturiert, sodass wir wissen, dass die Schwester irgendwann zum ENDIF gelangen wird.)
Da die beiden Computer miteinander kommunizieren können, können sie Notizen vergleichen und detailliert erklären, wie sich die beiden Eingabedatensätze und Ausführungsverläufe unterscheiden.
Bei der Differentialausführung (DE) wird dies natürlich mit einem Computer durchgeführt, wobei zwei simuliert werden.
Angenommen, Sie haben nur einen Satz von Eingabedaten, möchten aber sehen, wie sich dieser von Zeitpunkt 1 auf Zeitpunkt 2 geändert hat. Angenommen, das von Ihnen ausgeführte Programm ist ein Serializer / Deserializer. Während der Ausführung serialisieren (schreiben) Sie die aktuellen Daten aus und deserialisieren (lesen) die vergangenen Daten (die beim letzten Mal geschrieben wurden). Jetzt können Sie leicht erkennen, welche Unterschiede zwischen dem letzten Mal und dem letzten Mal bestehen.
Die Datei, in die Sie schreiben, und die alte Datei, aus der Sie lesen, bilden zusammen eine Warteschlange oder ein FIFO (First-In-First-Out), aber das ist kein sehr tiefgreifendes Konzept.
Mir kam der Gedanke, als ich an einem Grafikprojekt arbeitete, bei dem der Benutzer kleine Routinen für Anzeigeprozessoren erstellen konnte, die als "Symbole" bezeichnet wurden und zu größeren Routinen zusammengesetzt werden konnten, um beispielsweise Diagramme von Rohren, Tanks, Ventilen usw. zu malen. Wir wollten, dass die Diagramme "dynamisch" sind, da sie sich schrittweise aktualisieren können, ohne das gesamte Diagramm neu zeichnen zu müssen. (Die Hardware war nach heutigen Maßstäben langsam.) Ich erkannte, dass (zum Beispiel) eine Routine zum Zeichnen eines Balkens eines Balkendiagramms sich an seine alte Höhe erinnern und sich nur schrittweise aktualisieren konnte.
Das klingt nach OOP, nicht wahr? Anstatt jedoch ein "Objekt" zu "machen", könnte ich die Vorhersagbarkeit der Ausführungssequenz der Diagrammprozedur nutzen. Ich könnte die Höhe des Balkens in einem sequentiellen Byte-Stream schreiben. Um das Image zu aktualisieren, könnte ich die Prozedur einfach in einem Modus ausführen, in dem die alten Parameter nacheinander gelesen werden, während die neuen Parameter geschrieben werden, um für den nächsten Aktualisierungsdurchlauf bereit zu sein.
Dies scheint dumm zu sein und scheint zu brechen, sobald die Prozedur eine Bedingung enthält, da dann der neue und der alte Stream nicht mehr synchron sind. Aber dann wurde mir klar, dass sie wieder synchronisiert werden könnten, wenn sie auch den Booleschen Wert des bedingten Tests serialisieren würden . Es hat eine Weile gedauert, mich zu überzeugen und dann zu beweisen, dass dies immer funktionieren würde, vorausgesetzt, eine einfache Regel (die "Löschmodus-Regel") wird befolgt.
Das Nettoergebnis ist, dass der Benutzer diese "dynamischen Symbole" entwerfen und zu größeren Diagrammen zusammenfügen kann, ohne sich jemals Gedanken darüber machen zu müssen, wie sie dynamisch aktualisiert werden, unabhängig davon, wie komplex oder strukturell variabel die Anzeige sein würde.
In jenen Tagen musste ich mir Sorgen um Interferenzen zwischen visuellen Objekten machen, damit das Löschen eines Objekts anderen nicht schadet. Jetzt verwende ich die Technik jedoch mit Windows-Steuerelementen und lasse Windows Rendering-Probleme beheben.
Was erreicht es also? Dies bedeutet, dass ich einen Dialog erstellen kann, indem ich eine Prozedur zum Zeichnen der Steuerelemente schreibe, und ich muss mich nicht darum kümmern, die Steuerelementobjekte tatsächlich zu speichern oder sie schrittweise zu aktualisieren oder sie erscheinen / verschwinden / verschieben zu lassen, wenn die Bedingungen dies erfordern. Das Ergebnis ist ein um eine Größenordnung viel kleinerer und einfacherer Dialogquellcode, und Dinge wie das dynamische Layout oder das Ändern der Anzahl von Steuerelementen oder das Vorhandensein von Arrays oder Gittern von Steuerelementen sind trivial. Darüber hinaus kann ein Steuerelement wie ein Bearbeitungsfeld trivial an die Anwendungsdaten gebunden sein, die es bearbeitet, und es ist immer nachweislich korrekt, und ich muss mich nie mit seinen Ereignissen befassen. Das Einfügen eines Bearbeitungsfelds für eine Anwendungszeichenfolgenvariable ist eine einzeilige Bearbeitung.
- Warum ist es schwer zu verstehen?
Was ich am schwersten zu erklären fand, ist, dass man anders über Software denken muss. Programmierer sind so fest mit der Objekt-Aktions-Ansicht von Software verbunden, dass sie wissen möchten, was die Objekte sind, was die Klassen sind, wie sie die Anzeige "erstellen" und wie sie mit den Ereignissen umgehen, dass es eine Kirsche braucht Bombe, um sie heraus zu sprengen. Ich versuche zu vermitteln, dass es wirklich darauf ankommt, was Sie zu sagen haben.Stellen Sie sich vor, Sie erstellen eine domänenspezifische Sprache (DSL), in der Sie lediglich sagen müssen: "Ich möchte die Variable A hier, die Variable B dort und die Variable C dort unten bearbeiten", und sie würde sich auf magische Weise um Sie kümmern . In Win32 gibt es beispielsweise diese "Ressourcensprache" zum Definieren von Dialogen. Es ist ein perfektes DSL, außer dass es nicht weit genug geht. Es "lebt" nicht in der Hauptprozedursprache oder behandelt Ereignisse für Sie oder enthält Schleifen / Bedingungen / Unterprogramme. Aber es bedeutet gut, und Dynamic Dialogs versucht, den Job zu beenden.
Die andere Denkweise ist also: Um ein Programm zu schreiben, finden (oder erfinden) Sie zuerst ein geeignetes DSL und codieren so viel wie möglich von Ihrem Programm darin. Lassen Sie es sich mit allen Objekten und Aktionen befassen, die nur für die Implementierung existieren.
Wenn Sie die differenzielle Ausführung wirklich verstehen und verwenden möchten, gibt es einige knifflige Probleme, die Sie stören können. Ich habe es einmal in Lisp- Makros codiert , wo diese kniffligen Dinge für Sie erledigt werden könnten, aber in "normalen" Sprachen erfordert es etwas Programmiererdisziplin, um die Fallstricke zu vermeiden.
Tut mir leid, dass ich so langatmig bin. Wenn ich keinen Sinn ergeben habe, würde ich es begrüßen, wenn Sie darauf hinweisen und ich versuchen kann, es zu beheben.
Hinzugefügt:
In Java Swing gibt es ein Beispielprogramm namens TextInputDemo. Es ist ein statischer Dialog mit 270 Zeilen (ohne die Liste der 50 Zustände). In dynamischen Dialogen (in MFC) sind es ungefähr 60 Zeilen:
#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."));
}
Hinzugefügt:
Hier ist ein Beispielcode zum Bearbeiten einer Reihe von Krankenhauspatienten in etwa 40 Codezeilen. Die Zeilen 1-6 definieren die "Datenbank". Die Zeilen 10-23 definieren den Gesamtinhalt der Benutzeroberfläche. Die Zeilen 30-48 definieren die Steuerelemente zum Bearbeiten einer einzelnen Patientenakte. Beachten Sie, dass die Form des Programms Ereignisse zeitlich fast nicht berücksichtigt, als ob die Anzeige nur einmal erstellt werden müsste. Wenn dann Themen hinzugefügt oder entfernt werden oder andere strukturelle Änderungen stattfinden, wird es einfach erneut ausgeführt, als würde es von Grund auf neu erstellt, mit der Ausnahme, dass DE stattdessen eine inkrementelle Aktualisierung bewirkt. Der Vorteil ist, dass Sie als Programmierer keine Aufmerksamkeit schenken oder Code schreiben müssen, um die inkrementellen Aktualisierungen der Benutzeroberfläche durchzuführen, und sie sind garantiert korrekt. Es scheint, dass diese erneute Ausführung ein Leistungsproblem darstellt, ist es aber nicht.
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 }
Hinzugefügt: Brian hat eine gute Frage gestellt, und ich dachte, die Antwort gehört in den Haupttext hier:
@ Mike: Mir ist nicht klar, was die Anweisung "if (deButton (50, 20," Add ")) {" tatsächlich tut. Was macht die deButton-Funktion? Verwenden Ihre FOR / END-Schleifen auch ein Makro oder etwas anderes? - Brian.
@Brian: Ja, die FOR / END- und IF-Anweisungen sind Makros. Das SourceForge-Projekt ist vollständig implementiert. deButton behält eine Tastensteuerung bei. Wenn eine Benutzereingabeaktion stattfindet, wird der Code im Modus "Steuerereignis" ausgeführt, in dem deButton erkennt, dass er gedrückt wurde, und anzeigt, dass er gedrückt wurde, indem TRUE zurückgegeben wird. Der "if (deButton (...)) {... Aktionscode ...} ist daher eine Möglichkeit, Aktionscode an die Schaltfläche anzuhängen, ohne einen Abschluss erstellen oder einen Ereignishandler schreiben zu müssen. Der DD_THROW ist ein Methode zum Beenden des Durchlaufs, wenn die Aktion ausgeführt wird, da die Aktion möglicherweise Anwendungsdaten geändert hat. Daher ist es ungültig, den Durchlauf "Steuerereignis" durch die Routine fortzusetzen. Wenn Sie dies mit dem Schreiben von Ereignishandlern vergleichen, sparen Sie das Schreiben dieser. und Sie können beliebig viele Steuerelemente verwenden.
Hinzugefügt: Sorry, ich sollte erklären, was ich mit dem Wort "pflegt" meine. Bei der ersten Ausführung der Prozedur (im SHOW-Modus) erstellt deButton eine Schaltflächensteuerung und merkt sich deren ID im FIFO. Bei nachfolgenden Durchläufen (im UPDATE-Modus) erhält deButton die ID vom FIFO, ändert sie bei Bedarf und legt sie wieder im FIFO ab. Im ERASE-Modus liest es es aus dem FIFO, zerstört es und legt es nicht zurück, wodurch es "Müll sammelt". Der deButton-Aufruf verwaltet also die gesamte Lebensdauer des Steuerelements und hält es in Übereinstimmung mit den Anwendungsdaten, weshalb ich sage, dass er es "verwaltet".
Der vierte Modus ist EVENT (oder CONTROL). Wenn der Benutzer ein Zeichen eingibt oder auf eine Schaltfläche klickt, wird dieses Ereignis abgefangen und aufgezeichnet, und die Prozedur deContents wird im EVENT-Modus ausgeführt. deButton erhält die ID seiner Tastensteuerung vom FIFO und fragt, ob dies die Steuerung ist, auf die geklickt wurde. Wenn dies der Fall war, wird TRUE zurückgegeben, damit der Aktionscode ausgeführt werden kann. Wenn nicht, wird nur FALSE zurückgegeben. deEdit(..., &myStringVar)
Erkennt andererseits, ob das Ereignis dafür bestimmt war, und übergibt es in diesem Fall an das Bearbeitungssteuerelement. Anschließend wird der Inhalt des Bearbeitungssteuerelements in myStringVar kopiert. Zwischen dieser und der normalen UPDATE-Verarbeitung entspricht myStringVar immer dem Inhalt des Bearbeitungssteuerelements. So wird "gebunden". Die gleiche Idee gilt für Bildlaufleisten, Listenfelder, Kombinationsfelder und alle Steuerelemente, mit denen Sie Anwendungsdaten bearbeiten können.
Hier ist ein Link zu meiner Wikipedia-Bearbeitung: http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article