model-view-controller - 差异化执行如何工作?

标签 model-view-controller language-agnostic differential-execution

我在 Stack Overflow 上看到了一些关于这个的提及,但盯着维基百科(相关页面已被删除)和 MFC dynamic dialog demo 没有任何启发我。有人可以解释一下吗?学习一个根本不同的概念听起来不错。

基于这些答案:我想我对它有了更好的感觉。我想我只是第一次没有仔细查看源代码。在这一点上,我对差异化执行有复杂的感觉。一方面,它可以使某些任务变得更加容易。另一方面,让它启动并运行(也就是说,用你选择的语言设置它)并不容易(我相信如果我理解得更好的话)......尽管我猜它的工具箱只需要制作一次,然后根据需要进行扩展。我想为了真正理解它,我可能需要尝试用另一种语言来实现它。

最佳答案

哎呀,布赖恩,我希望我早点看到你的问题。因为这几乎是我的
“发明”(无论好坏),我也许可以提供帮助。

Inserted: The shortest possible explanation I can make is that if normal execution is like throwing a ball in the air and catching it, then differential execution is like juggling.



@windfinder 的解释和我的不一样,没关系。这种技术不容易理解,我花了大约 20 年(断断续续)找到有效的解释。让我在这里再试一次:
  • 是什么?

  • 我们都理解计算机逐步执行程序、根据输入数据执行条件分支并执行操作的简单想法。 (假设我们只处理简单的结构化无 goto、无返回代码。)该代码包含语句序列、基本结构化条件、简单循环和子例程调用。 (暂时忘记返回值的函数。)

    现在想象两台计算机彼此同步执行相同的代码,并且能够比较注释。计算机 1 使用输入数据 A 运行,计算机 2 使用输入数据 B 运行。它们并排运行。如果他们得到像 IF(test) .... ENDIF 这样的条件语句,并且如果他们对测试是否为真有不同意见,那么说测试为假的人会跳到 ENDIF 并等待它的妹妹要 catch 。 (这就是代码结构化的原因,所以我们知道姐姐最终会到达 ENDIF。)

    由于两台计算机可以相互交谈,因此它们可以比较笔记并详细解释两组输入数据和执行历史的不同之处。

    当然,在差分执行 (DE) 中,它是用一台计算机完成的,模拟两台计算机。

    现在,假设您只有一组输入数据,但您想查看它从时间 1 到时间 2 的变化情况。假设您正在执行的程序是一个序列化器/反序列化器。执行时,您既序列化(写出)当前数据,又反序列化(读入)过去的数据(上次执行此操作时写入的数据)。现在,您可以轻松查看上次数据与这次数据之间的差异。

    您正在写入的文件和您正在读取的旧文件一起构成了一个队列或 FIFO(先进先出),但这不是一个很深的概念。
  • 它有什么用?

  • 在我从事图形项目时,我想到了这一点,在该项目中,用户可以构建称为“符号”的小显示处理器例程,这些例程可以组装成更大的例程来绘制诸如管道、 jar 、阀门之类的图表之类的东西。我们希望图表是“动态的”,因为它们可以逐步更新自己而无需重新绘制整个图表。 (按照今天的标准,硬件很慢。)我意识到(例如)绘制条形图条形的例程可以记住它的旧高度,并且只是逐步更新自身。

    这听起来像 OOP,不是吗?然而,我可以利用图表过程执行顺序的可预测性,而不是“制作”一个“对象”。我可以在顺序字节流中写入条的高度。然后为了更新图像,我可以在一种模式下运行该过程,它在写入新参数的同时顺序读取其旧参数,以便为下一次更新传递做好准备。

    这似乎非常明显,而且一旦过程包含条件,似乎就会中断,因为新流和旧流将不同步。但后来我突然意识到,如果他们还序列化了条件测试的 bool 值,他们就可以恢复同步。
    我花了一段时间说服自己,然后证明,只要遵循一个简单的规则(“删除模式规则”),这总是有效的。

    最终结果是,用户可以设计这些“动态符号”并将它们组合成更大的图表,而不必担心它们将如何动态更新,无论显示多么复杂或结构可变。

    那个时候,我确实需要担心视觉对象之间的干扰,以便删除一个不会损坏另一个。但是,现在我将该技术用于 Windows 控件,并让 Windows 处理渲染问题。

    那么它实现了什么?这意味着我可以通过编写一个程序来绘制控件来构建一个对话框,而且我不必担心实际记住控件对象或处理增量更新它们,或者使它们在条件允许时出现/消失/移动。结果是更小更简单的对话框源代码,大约一个数量级,动态布局或更改控件数量或控件数组或网格等事情是微不足道的。此外,诸如 Edit 字段之类的控件可以简单地绑定(bind)到它正在编辑的应用程序数据,而且它总是可以证明是正确的,而且我永远不必处理它的事件。为应用程序字符串变量放入编辑字段是一种单行编辑。
  • 为什么很难理解?

  • 我发现最难解释的是,它需要对软件进行不同的思考。程序员如此执着于软件的对象- Action View ,以至于他们想知道对象是什么,类是什么,他们如何“构建”显示,以及他们如何处理事件,这需要一颗樱桃炸弹把他们炸出来。我想传达的是,真正重要的是你需要说什么?想象一下,您正在构建一种特定于领域的语言 (DSL),您只需告诉它“我想在这里编辑变量 A,在那里编辑变量 B,在那里编辑变量 C”,它会神奇地为您处理它.例如,在 Win32 中有用于定义对话框的“资源语言”。这是一个非常好的 DSL,只是它做得还不够远。它不“存在于”主要过程语言中,也不为您处理事件,也不包含循环/条件/子例程。但这意味着很好,Dynamic Dialogs 试图完成这项工作。

    所以,不同的思维模式是: 写一个程序,你首先找到(或发明)一个合适的 DSL,并尽可能多地编写你的程序。让 处理所有仅为实现而存在的对象和 Action 。

    如果您想真正了解差分执行并使用它,有几个棘手的问题可能会绊倒您。我曾经用 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 }
    

    补充:布赖恩问了一个很好的问题,我认为答案属于这里的正文:

    @Mike:我不清楚“if (deButton(50, 20, “Add”)){” 语句实际上在做什么。 deButton 函数有什么作用?另外,您的 FOR/END 循环是否使用某种宏或其他东西? ——布莱恩。

    @Brian:是的,FOR/END 和 IF 语句是宏。 SourceForge 项目有一个完整的实现。 deButton 维护一个按钮控件。当任何用户输入 Action 发生时,代码在“控制事件”模式下运行,在这种模式下 deButton 检测到它被按下并通过返回 TRUE 来表示它被按下。因此,“if(deButton(...)){... action code ...} 是一种将操作代码附加到按钮的方式,而无需创建闭包或编写事件处理程序。DD_THROW 是一个采取 Action 时终止传递的方式,因为 Action 可能修改了应用程序数据,因此继续“控制事件”传递通过例程是无效的。如果将其与编写事件处理程序进行比较,则可以节省您编写那些,它可以让您拥有任意数量的控件。

    补充:对不起,我应该解释一下“维护”这个词的意思。当程序第一次执行时(在 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 始终等于编辑控件的内容。这就是“绑定(bind)”的完成方式。同样的想法也适用于滚动条、列表框、组合框以及任何可以让您编辑应用程序数据的控件。

    这是我的维基百科编辑的链接:http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article

    关于model-view-controller - 差异化执行如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/371898/

    相关文章:

    c# - 我如何将一个 View 窗体传输到 MVC3 中的另一个 View ?

    language-agnostic - 如何在纸上高效地编写代码

    algorithm - 什么是日历队列?

    algorithm - 求解非线性丢番图方程,例如 (8+3n)m = 11?

    c++ - 是否可以执行差异链接?

    javascript 对象属性类型仅接受数字

    javascript - 单击按钮后使用 angularJS 获取模型的原始值

    c# - 重定向到新选项卡中的页面时避免弹出窗口拦截器