Gee, Brian, tôi ước tôi thấy câu hỏi của bạn sớm hơn. Vì nó khá giống "phát minh" của tôi (tốt hơn hoặc tệ hơn), tôi có thể giúp.
Đã chèn: Lời giải thích ngắn gọn nhất mà tôi có thể đưa ra là nếu thực hiện thông thường giống như ném một quả bóng lên không trung và bắt lấy nó, thì thực hiện vi sai giống như tung hứng.
Lời giải thích của @ windfinder khác với của tôi, và điều đó không sao cả. Kỹ thuật này không dễ để hiểu hết mọi người và tôi đã mất khoảng 20 năm (lặp đi lặp lại) để tìm ra lời giải thích hiệu quả. Hãy để tôi cung cấp cho nó một shot khác ở đây:
Tất cả chúng ta đều hiểu ý tưởng đơn giản về một chiếc máy tính chạy qua một chương trình, lấy các nhánh có điều kiện dựa trên dữ liệu đầu vào và thực hiện mọi việc. (Giả sử chúng ta chỉ xử lý mã goto-less, return-less có cấu trúc đơn giản.) Đoạn mã đó chứa các chuỗi câu lệnh, điều kiện có cấu trúc cơ bản, vòng lặp đơn giản và lệnh gọi chương trình con. (Hãy quên các hàm trả về giá trị ngay bây giờ.)
Bây giờ, hãy tưởng tượng hai máy tính thực thi cùng một mã đó trong bước khóa với nhau và có thể so sánh các ghi chú. Máy tính 1 chạy với dữ liệu đầu vào A và Máy tính 2 chạy với dữ liệu đầu vào B. Chúng chạy từng bước một. Nếu họ đến với một câu lệnh điều kiện như IF (test) .... ENDIF và nếu họ có ý kiến khác nhau về việc kiểm tra có đúng hay không, thì người nói kiểm tra nếu sai sẽ bỏ qua ENDIF và đợi xung quanh em gái của nó để bắt kịp. (Đây là lý do tại sao mã được cấu trúc, vì vậy chúng tôi biết chị em cuối cùng sẽ nhận được ENDIF.)
Vì hai máy tính có thể nói chuyện với nhau nên chúng có thể so sánh các ghi chú và đưa ra lời giải thích chi tiết về việc hai bộ dữ liệu đầu vào và lịch sử thực thi khác nhau như thế nào.
Tất nhiên, trong thực thi vi phân (DE), nó được thực hiện với một máy tính, mô phỏng hai.
NGAY BÂY GIỜ, giả sử bạn chỉ có một bộ dữ liệu đầu vào, nhưng bạn muốn xem nó đã thay đổi như thế nào từ lần 1 sang lần 2. Giả sử chương trình bạn đang thực hiện là bộ nối tiếp / bộ khử không khí. Khi bạn thực thi, bạn vừa tuần tự hóa (ghi ra) dữ liệu hiện tại vừa giải mã hóa (đọc trong) dữ liệu quá khứ (được ghi vào lần cuối cùng bạn làm điều này). Bây giờ bạn có thể dễ dàng thấy sự khác biệt giữa dữ liệu lần trước và dữ liệu lần này là gì.
Tệp bạn đang ghi và tệp cũ bạn đang đọc từ đó, được ghép lại với nhau tạo thành một hàng đợi hoặc FIFO (nhập trước xuất trước), nhưng đó không phải là một khái niệm sâu sắc.
Điều đó xảy ra với tôi khi tôi đang làm việc trong một dự án đồ họa, nơi người dùng có thể xây dựng các quy trình nhỏ của bộ xử lý hiển thị được gọi là "ký hiệu" có thể được lắp ráp thành các quy trình lớn hơn để vẽ những thứ như sơ đồ đường ống, bể chứa, van, những thứ tương tự. Chúng tôi muốn các sơ đồ "năng động" theo nghĩa là chúng có thể tự cập nhật dần dần mà không cần phải vẽ lại toàn bộ sơ đồ. (Phần cứng chậm theo tiêu chuẩn ngày nay.) Tôi nhận ra rằng (ví dụ) một thói quen vẽ một thanh của biểu đồ thanh có thể nhớ chiều cao cũ của nó và chỉ cần cập nhật dần dần.
Điều này nghe giống như OOP, phải không? Tuy nhiên, thay vì "tạo" một "đối tượng", tôi có thể tận dụng khả năng dự đoán của trình tự thực thi của thủ tục sơ đồ. Tôi có thể viết chiều cao của thanh trong một luồng byte tuần tự. Sau đó, để cập nhật hình ảnh, tôi chỉ có thể chạy thủ tục ở chế độ mà nó tuần tự đọc các thông số cũ trong khi nó ghi các thông số mới để sẵn sàng cho lần cập nhật tiếp theo.
Điều này có vẻ rõ ràng một cách ngu ngốc và dường như sẽ bị phá vỡ ngay khi thủ tục chứa một điều kiện, vì khi đó luồng mới và luồng cũ sẽ không đồng bộ. Nhưng sau đó tôi nhận ra rằng nếu họ cũng tuần tự hóa giá trị boolean của bài kiểm tra điều kiện, họ có thể đồng bộ trở lại . Phải mất một lúc để thuyết phục bản thân, và sau đó chứng minh rằng điều này sẽ luôn hoạt động, với điều kiện tuân theo một quy tắc đơn giản ("quy tắc chế độ xóa").
Kết quả thực tế là người dùng có thể thiết kế các "ký hiệu động" này và lắp ráp chúng thành các biểu đồ lớn hơn mà không cần phải lo lắng về cách chúng sẽ cập nhật động, bất kể màn hình có biến đổi cấu trúc hoặc phức tạp đến mức nào.
Vào những ngày đó, tôi đã phải lo lắng về sự giao thoa giữa các vật thể trực quan, để việc xóa một vật thể sẽ không làm hỏng những vật thể khác. Tuy nhiên, bây giờ tôi sử dụng kỹ thuật này với các điều khiển Windows và tôi để Windows xử lý các vấn đề kết xuất.
Vậy nó đạt được những gì? Điều đó có nghĩa là tôi có thể xây dựng một hộp thoại bằng cách viết một thủ tục để vẽ các điều khiển và tôi không phải lo lắng về việc thực sự ghi nhớ các đối tượng điều khiển hoặc xử lý việc cập nhật từng bước chúng, hoặc làm cho chúng xuất hiện / biến mất / di chuyển khi có điều kiện. Kết quả là mã nguồn hộp thoại nhỏ hơn và đơn giản hơn nhiều, theo thứ tự độ lớn, và những thứ như bố cục động hoặc thay đổi số lượng điều khiển hoặc có mảng hoặc lưới điều khiển là không đáng kể. Ngoài ra, một điều khiển chẳng hạn như trường Chỉnh sửa có thể bị ràng buộc nhỏ với dữ liệu ứng dụng mà nó đang chỉnh sửa và nó sẽ luôn đúng một cách có thể chứng minh được và tôi không bao giờ phải xử lý các sự kiện của nó. Đưa vào trường chỉnh sửa cho biến chuỗi ứng dụng là chỉnh sửa một dòng.
Điều tôi thấy khó giải thích nhất là nó đòi hỏi tư duy khác về phần mềm. Các lập trình viên gắn bó chặt chẽ với chế độ xem hành động đối tượng của phần mềm đến mức họ muốn biết đâu là đối tượng, đâu là lớp, cách họ "xây dựng" màn hình và cách họ xử lý các sự kiện, điều đó phải mất một thời gian. bom để nổ chúng ra khỏi nó. Điều tôi cố gắng truyền đạt là điều thực sự quan trọng là bạn cần nói gì?Hãy tưởng tượng bạn đang xây dựng một ngôn ngữ dành riêng cho miền (DSL) mà tất cả những gì bạn cần làm là nói với nó "Tôi muốn sửa biến A ở đây, biến B ở đó và biến C ở đó" và nó sẽ xử lý nó một cách kỳ diệu cho bạn . Ví dụ, trong Win32 có "ngôn ngữ tài nguyên" này để xác định hộp thoại. Nó là một DSL hoàn toàn tốt, ngoại trừ nó không đi đủ xa. Nó không "sống trong" ngôn ngữ thủ tục chính hoặc xử lý các sự kiện cho bạn hoặc chứa các vòng lặp / điều kiện / chương trình con. Nhưng nó có nghĩa là tốt và Hộp thoại động sẽ cố gắng hoàn thành công việc.
Vì vậy, cách suy nghĩ khác là: để viết một chương trình, trước tiên bạn phải tìm (hoặc phát minh ra) một DSL thích hợp và viết mã chương trình của bạn vào đó càng nhiều càng tốt. Để nó xử lý tất cả các đối tượng và hành động chỉ tồn tại cho mục đích triển khai.
Nếu bạn muốn thực sự hiểu về thực thi vi phân và sử dụng nó, có một số vấn đề phức tạp có thể khiến bạn khó chịu. Tôi đã từng mã hóa nó trong các macro Lisp , nơi những bit phức tạp này có thể được xử lý cho bạn, nhưng trong các ngôn ngữ "bình thường", nó đòi hỏi một số kỷ luật của lập trình viên để tránh những cạm bẫy.
Xin lỗi vì đã dài dòng như vậy. Nếu tôi không hiểu, tôi sẽ đánh giá cao nếu bạn chỉ ra và tôi có thể thử và sửa nó.
Thêm:
Trong Java Swing , có một chương trình ví dụ được gọi là TextInputDemo. Nó là một hộp thoại tĩnh, có 270 dòng (không tính danh sách 50 trạng thái). Trong Hộp thoại động (trong MFC), khoảng 60 dòng:
#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."));
}
Thêm:
Đây là mã ví dụ để chỉnh sửa một mảng bệnh nhân trong bệnh viện trong khoảng 40 dòng mã. Các dòng 1-6 xác định "cơ sở dữ liệu". Các dòng 10-23 xác định nội dung tổng thể của giao diện người dùng. Các dòng 30-48 xác định các điều khiển để chỉnh sửa hồ sơ của một bệnh nhân. Lưu ý rằng hình thức của chương trình hầu như không có thông báo về các sự kiện trong thời gian, như thể tất cả những gì nó phải làm là tạo màn hình một lần. Sau đó, nếu các đối tượng được thêm vào hoặc bị xóa hoặc các thay đổi cấu trúc khác diễn ra, nó sẽ đơn giản được thực thi lại, như thể nó đang được tạo lại từ đầu, ngoại trừ việc DE khiến quá trình cập nhật gia tăng diễn ra thay thế. Ưu điểm của bạn là lập trình viên không phải chú ý hoặc viết bất kỳ mã nào để thực hiện các cập nhật gia tăng của giao diện người dùng và chúng được đảm bảo chính xác. Có vẻ như việc thực thi lại này sẽ là một vấn đề về hiệu suất, nhưng không phải vậy,
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 }
Đã thêm: Brian đã hỏi một câu hỏi hay và tôi nghĩ câu trả lời thuộc về văn bản chính ở đây:
@Mike: Tôi không rõ câu lệnh "if (deButton (50, 20," Add ")) {" thực sự đang làm gì. Hàm deButton làm gì? Ngoài ra, các vòng lặp FOR / END của bạn có sử dụng một số loại macro hay gì đó không? - Brian.
@Brian: Có, câu lệnh FOR / END và IF là macro. Dự án SourceForge đã được triển khai hoàn chỉnh. deButton duy trì một nút điều khiển. Khi bất kỳ hành động nhập của người dùng nào diễn ra, mã sẽ được chạy ở chế độ "sự kiện điều khiển", trong đó deButton phát hiện rằng nó đã được nhấn và cho biết rằng nó đã được nhấn bằng cách trả về TRUE. Do đó, "if (deButton (...)) {... mã hành động ...} là một cách để gắn mã hành động vào nút mà không cần phải tạo một bao đóng hoặc viết một trình xử lý sự kiện. DD_THROW là một cách kết thúc quá trình chuyển khi hành động được thực hiện vì hành động có thể đã sửa đổi dữ liệu ứng dụng, do đó, việc tiếp tục chuyển "sự kiện điều khiển" qua quy trình là không hợp lệ. Nếu bạn so sánh điều này với việc viết các trình xử lý sự kiện, nó sẽ giúp bạn viết chúng, và nó cho phép bạn có bất kỳ số lượng điều khiển nào.
Đã thêm: Xin lỗi, tôi nên giải thích ý của tôi bằng từ "duy trì". Khi thủ tục được thực thi lần đầu tiên (ở chế độ HIỂN THỊ), deButton tạo điều khiển nút và ghi nhớ id của nó trong FIFO. Trong các lần chuyển tiếp theo (ở chế độ CẬP NHẬT), deButton lấy id từ FIFO, sửa đổi nó nếu cần và đưa nó trở lại FIFO. Trong chế độ ERASE, nó đọc nó từ FIFO, phá hủy nó và không đặt nó trở lại, do đó "thu thập rác" nó. Vì vậy, lệnh gọi deButton quản lý toàn bộ thời gian tồn tại của điều khiển, giữ nó phù hợp với dữ liệu ứng dụng, đó là lý do tại sao tôi nói nó "duy trì" nó.
Chế độ thứ tư là EVENT (hoặc CONTROL). Khi người dùng nhập một ký tự hoặc nhấp vào một nút, sự kiện đó sẽ được bắt và ghi lại, sau đó thủ tục deContents được thực thi ở chế độ EVENT. deButton lấy id của điều khiển nút của nó từ FIFO và hỏi nếu đây là điều khiển đã được nhấp. Nếu đúng, nó trả về TRUE để mã hành động có thể được thực thi. Nếu không, nó chỉ trả về FALSE. Mặt khác, deEdit(..., &myStringVar)
phát hiện xem sự kiện có dành cho nó hay không và nếu có thì chuyển nó đến điều khiển chỉnh sửa, rồi sao chép nội dung của điều khiển chỉnh sửa vào myStringVar. Giữa quá trình này và quá trình UPDATE thông thường, myStringVar luôn cân bằng nội dung của điều khiển chỉnh sửa. Đó là cách "ràng buộc" được thực hiện. Ý tưởng tương tự cũng áp dụng cho thanh cuộn, hộp danh sách, hộp tổ hợp, bất kỳ loại điều khiển nào cho phép bạn chỉnh sửa dữ liệu ứng dụng.
Đây là liên kết đến bản chỉnh sửa Wikipedia của tôi: http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article