21

我在网上找到了这段代码,里面有一个过程。我不明白为什么作者会选择这样写。我注意到的是正在执行的递归函数。

为什么他不像我见过的大多数代码那样分开程序。

他的实现:

procedure XML2Form(tree : TJvPageListTreeView; XMLDoc : TXMLDocument);
var
  iNode : IXMLNode;

  procedure ProcessNode(
    Node : IXMLNode; 
    tn   : TTreeNode);
  var
    cNode : IXMLNode;
  begin
    if Node = nil then Exit;
    with Node do
    begin
      tn := tree.Items.AddChild(tn, Attributes['text']);
      tn.ImageIndex := Integer(Attributes['imageIndex']);
      tn.StateIndex := Integer(Attributes['stateIndex']);
    end;

    cNode := Node.ChildNodes.First;
    while cNode <> nil do
    begin
      ProcessNode(cNode, tn);
      cNode := cNode.NextSibling;
    end;
  end; (*ProcessNode*) 
begin
  tree.Items.Clear;
  XMLDoc.FileName := ChangeFileExt(ParamStr(0),'.XML');
  XMLDoc.Active := True;

  iNode := XMLDoc.DocumentElement.ChildNodes.First;

  while iNode <> nil do
  begin
    ProcessNode(iNode,nil);
    iNode := iNode.NextSibling;
  end;
  XMLDoc.Active := False;
end; (* XML2Form *)


procedure Form2XML(tree: TJVPageListTreeView);
var
  tn : TTreeNode;
  XMLDoc : TXMLDocument;
  iNode : IXMLNode;

  procedure ProcessTreeItem(
    tn    : TTreeNode;
    iNode : IXMLNode);
  var
    cNode : IXMLNode;
  begin
    if (tn = nil) then Exit;
    cNode := iNode.AddChild('item');
    cNode.Attributes['text'] := tn.Text;
    cNode.Attributes['imageIndex'] := tn.ImageIndex;
    cNode.Attributes['stateIndex'] := tn.StateIndex;
    cNode.Attributes['selectedIndex'] := tn.SelectedIndex;

    //child nodes
    tn := tn.getFirstChild;
    while tn <> nil do
    begin
      ProcessTreeItem(tn, cNode);
      tn := tn.getNextSibling;
    end;
  end; (*ProcessTreeItem*)
begin
  XMLDoc := TXMLDocument.Create(nil);
  XMLDoc.Active := True;
  iNode := XMLDoc.AddChild('tree2xml');
  iNode.Attributes['app'] := ParamStr(0);

  tn := tree.TopItem;
  while tn <> nil do
  begin
    ProcessTreeItem (tn, iNode);

    tn := tn.getNextSibling;
  end;

  XMLDoc.SaveToFile(ChangeFileExt(ParamStr(0),'.XML'));
  XMLDoc := nil;
end; (* Form2XML *)

或修改后的实现:

procedure ProcessNode(Node : IXMLNode; tn : TTreeNode);
var
  cNode : IXMLNode;
begin
  if Node = nil then Exit;
  with Node do
  begin
    tn := tree.Items.AddChild(tn, Attributes['text']);
    tn.ImageIndex := Integer(Attributes['imageIndex']);
    tn.StateIndex := Integer(Attributes['stateIndex']);
  end;

  cNode := Node.ChildNodes.First;
  while cNode <> nil do
  begin
    ProcessNode(cNode, tn);
    cNode := cNode.NextSibling;
  end;
end; (*ProcessNode*)

procedure ProcessTreeItem(tn : TTreeNode; iNode : IXMLNode);
var
  cNode : IXMLNode;
begin
  if (tn = nil) then Exit;
  cNode := iNode.AddChild('item');
  cNode.Attributes['text'] := tn.Text;
  cNode.Attributes['imageIndex'] := tn.ImageIndex;
  cNode.Attributes['stateIndex'] := tn.StateIndex;
  cNode.Attributes['selectedIndex'] := tn.SelectedIndex;

  //child nodes
  tn := tn.getFirstChild;
  while tn <> nil do
  begin
    ProcessTreeItem(tn, cNode);
    tn := tn.getNextSibling;
  end;
end; (*ProcessTreeItem*)

procedure XML2Form(tree : TJvPageListTreeView; XMLDoc : TXMLDocument);
var
  iNode : IXMLNode;
begin
  tree.Items.Clear;
  XMLDoc.FileName := ChangeFileExt(ParamStr(0),'.XML');
  XMLDoc.Active := True;

  iNode := XMLDoc.DocumentElement.ChildNodes.First;

  while iNode <> nil do
  begin
    ProcessNode(iNode,nil);
    iNode := iNode.NextSibling;
  end;
  XMLDoc.Active := False;
end;

procedure Form2XML(tree: TJVPageListTreeView);
var
  tn : TTreeNode;
  XMLDoc : TXMLDocument;
  iNode : IXMLNode;
begin
  XMLDoc := TXMLDocument.Create(nil);
  XMLDoc.Active := True;
  iNode := XMLDoc.AddChild('tree2xml');
  iNode.Attributes['app'] := ParamStr(0);

  tn := tree.TopItem;
  while tn <> nil do
  begin
    ProcessTreeItem (tn, iNode);

    tn := tn.getNextSibling;
  end;

  XMLDoc.SaveToFile(ChangeFileExt(ParamStr(0),'.XML'));
  XMLDoc := nil;
end; (* Form2XML *)
4

4 回答 4

19

像这样的嵌套过程在这个与 XML 相关的代码中是有意义的。要处理所有节点,需要递归调用of ProcessNode。您必须注意,有时,内部函数需要访问比几个参数更多的数据。

潜在的实现可能是:

  • 在你的实现中使用“扁平化”程序;
  • 使用“嵌套”过程,就像在原始实现中一样;
  • 创建一个专用的class(或record+ 方法),它将对单元的一部分保持私有。implementation

当然,第 3 个选项听起来更易于维护。它将允许明确分离过程,允许使用其方法的局部变量。使用 a record(或object旧版本的 Delphi )将允许在主过程的堆栈上分配处理对象,因此您不需要编写Obj := TInterType.Create; try .. finally Obj.Free. 但是如果你使用object请注意一些新版本的 Delphi有编译问题- 你应该更好地使用record方法。

恕我直言,“扁平”过程风格并不比“嵌套”过程好,甚至更糟,因为它需要向内部调用添加额外的参数,或者使用一些全局变量。顺便说一句,每次调用都有很多变量会增加堆栈空间并降低速度。

“嵌套”样式实际上是面向 OOP 的。当调用内部函数时,编译器将寄存器中的调用者堆栈基址传递给嵌套函数(就像self对象的附加参数一样)。因此内部函数能够访问所有调用者堆栈变量,就像它们在私有对象中声明一样(第三种解决方案)。

Delphi IDE 和内部调试器可以很好地处理嵌套过程。恕我直言,对于一些小段代码(即可以在相同屏幕高度上读取的内容)可能有意义。然后,当您需要更多进程时,专用record/object的方法和显式变量将更易于维护。但恕我直言,“平面”选项不可编码。

我刚刚写了一篇关于这些实现模式的博客文章,它将展示一些QuickSort实现的源代码,它将使用尽可能少的堆栈空间,并避免调用过程中的嵌套过程,并使用专用私人object代替。

在所有情况下,不要害怕创建一些内部对象/类来实现您的算法。最新版本的 Delphi 甚至允许在class定义中使用私有类型 - 但有时,我更愿意让内部对象implementation对单元的一部分完全私有,即甚至不作为单元部分的私有成员出现interface

类不仅用于在单元之外发布您的流程:OOP 也适用于实现模式。您的代码将更易于维护,并且在大多数情况下,该self参数将用于一次引用所有关联的数据,因此您的代码也可能更快更轻!

于 2012-05-20T07:40:28.987 回答
13

使用这样的内部程序进行编码是一种风格问题。有人可能会争辩说它“更干净”……就像将所有相关数据和例程封装在一个东西中一样,就像人们听说的“面向对象编程”一样……但它也有缺点:更难编码最初正确,更难测试,对许多程序员来说更难理解(因此可能更难维护)。

定义内部过程可以防止未来的程序员意外调用内部过程并期望它做一些合理的事情。该内部过程甚至没有在外部/全局级别上定义 - 因此不能被调用。

定义内部过程还意味着外部/全局命名空间中名称冲突的可能性较小,因为内部例程对该命名空间没有任何贡献。(这是一个很好的例子:可能有多少种不同的东西都命名为“ProcessNode(...)”?)

并且如前所述,在大多数语言中,内部例程对原本不可见的本地数据类型和变量具有“特殊”访问权限。

于 2012-05-20T05:12:51.857 回答
4

您的修订版本存在一个问题:它引用tree了 main 方法的参数。这是可以通过嵌套过程完成的一件事:它们可以从到目前为止已声明的外部范围访问任何变量。

话虽如此,许多开发人员发现嵌套过程是一种凌乱的编码风格,并宁愿避免它;他们通常会像您一样重写它,但将tree其作为另一个参数添加到ProcessNode.

于 2012-05-20T04:49:47.813 回答
3

嵌套过程/函数早在添加 OOP 之前就已经在 Delphi 中可用。这一切都发生在大约 25 年前。回到那个时代,函数内部的局部函数有助于保持全局范围更清晰,相关代码更紧密地结合在一起。Borland / Inprise / Embarcadero 从未放弃该功能,当然因为否则它们会造成巨大的不兼容性。因此,如果对您有意义,请使用它,否则就顺其自然。

于 2013-12-21T19:31:35.840 回答