程序内的程序?

时间:2012-05-20 04:45:13

标签: delphi

我在网上发现这个代码在程序中有一个程序。 我不明白为什么作者会选择这样写。 我注意到的是正在执行的递归函数。

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

他的实施:

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 个答案:

答案 0 :(得分:18)

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

潜在的实施可能是:

  • 使用“平面”程序,如在您的实施中;
  • 使用“嵌套”程序,如原始实现中那样;
  • 创建一个专用的class(或record +方法),这些方法将私有保留到该单元的implementation部分。

当然,第三种选择听起来更容易维护。它将允许明确分离过程,允许使用其方法本地的变量。对旧版本的Delphi使用record(或object)将允许在主过程的堆栈上分配处理对象,因此您不需要编写Obj := TInterType.Create; try .. finally Obj.Free 。但是如果您使用object请注意某些新版本的Delphi has compilation issue - 您应该更好地使用record方法。

“扁平”程序样式是恕我直言并不比“嵌套”程序更好,更糟糕的是,因为它需要在内部调用中添加其他参数,或者使用一些全局变量。顺便说一句,每次调用都有很多变量会增加堆栈空间,并降低速度。

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

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

我只是written a blog article about these implementation patterns,它将提供 QuickSort 实现的一些源代码,它将尽可能少地使用堆栈空间,并且将避免调用嵌套过程在程序中,并使用专用的私人object

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

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

答案 1 :(得分:11)

使用类似内部程序进行编码是一种风格问题。有人可能会认为它是“更干净”......就像在“面向对象的编程”中听到的那样,将一个内的所有相关数据和例程封装起来的同样意义......但它也是有缺点:最初编码更难,更难测试,许多程序员更难理解(因此可能更难维护)。

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

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

如上所述,在大多数语言中,内部例程具有“特殊”访问权限,否则将是不可见的本地数据类型和变量。

答案 2 :(得分:4)

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

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

答案 3 :(得分:1)

在添加OOP之前很久就已经在Delphi中提供了嵌套过程/函数。这一切都发生在大约25年前。回到那时,函数内部的本地函数有助于使全局范围更清晰,相关代码更紧密。 Borland / Inprise / Embarcadero从未放弃过该功能,当然因为否则它们会产生巨大的不兼容性。 所以,如果它对你有用,请使用它,否则就让它成为。