我在网上找到了这段代码,它在过程中有一个过程。我不明白为什么作者会选择这样写。我注意到的是执行了一个递归函数。
为什么他不像我见过的大多数代码那样将程序分开。
他的实施:
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 *)
5条答案
按热度按时间qpgpyjmq1#
这样的嵌套过程在这段与XML相关的代码中确实有意义。要处理所有节点,需要递归调用
ProcessNode
。您必须注意,有时,内部函数需要访问比几个参数多得多的数据。潜在的实施可能包括:
class
(或record
+方法),该class
(或record
+方法)将对单元的implementation
部分保持私有。当然,第三种选择听起来更易于维护。它将允许清楚地分离过程,和允许使用其方法的局部变量。使用
record
(或较早版本的Delphi使用object
)将允许在主过程的堆栈上分配处理对象,因此您不需要编写Obj := TInterType.Create; try .. finally Obj.Free
。但如果您使用object
,请注意一些新版本的Delphi has compilation issue-您最好将record
与方法一起使用。“平面”过程风格并不比“嵌套”过程好,甚至更糟,因为它需要向内部调用添加额外的参数,或者使用一些全局变量。顺便说一句,每次调用都有很多变量会增加堆栈空间,降低速度。
“嵌套”风格实际上是面向对象的。调用内部函数时,编译器会将寄存器中的调用方堆栈基传递给嵌套函数(就像对象的附加
self
参数一样)。因此,内部函数能够访问所有调用方堆栈变量,就像它们是在私有对象中声明的一样(第三个解决方案)。Delphi IDE和内部调试器可以很好地处理嵌套过程。对于一些小代码(即,可以在相同屏幕高度上阅读的代码)来说,这可能是有意义的。然后,当您需要更多进程时,带有方法和显式变量的专用
record/object
将更易于维护。但“平面”选项是不能编码的。我刚刚介绍了written a blog article about these implementation patterns,它将提供一个快速排序实现的一些源代码,它将使用尽可能少的堆栈空间,并将避免调用过程中的嵌套过程,而是使用专用的私有
object
。在所有情况下,不要害怕创建一些内部对象/类来实现您的算法。Delphi的最新版本甚至允许
class
定义中的私有类型-但有时,我更喜欢将内部对象完全私有于单元的implementation
部分,即Non甚至显示为单元的interface
部分的私有成员。类不仅仅用于在单元之外发布您的流程:OOP也适用于实现模式。您的代码将更易于维护,并且在大多数情况下,
self
参数将用于一次引用所有相关数据,因此您的代码可能会更快、更轻!ymdaylpp2#
用这样的内部过程编写代码是一个风格问题。有人可能会争辩说,它更“干净”……就像人们听说的“面向对象编程”一样,把所有相关的数据和例程封装在一个东西里……但它也有缺点:最初更难正确编码,更难测试,对许多程序员来说更难理解(因此可能更难维护)。
定义内部过程可以防止未来的程序员意外地调用内部过程并期望它做一些合理的事情。内部过程甚至没有在外部/全局级别定义-因此不能被调用。
定义内部过程也意味着在外部/全局命名空间中发生名称冲突的可能性较小,因为内部例程不会对该命名空间做出任何贡献。(这是一个很好的例子:有多少不同的东西都命名为“ProcessNode(...)”很有可能?)
正如前面提到的,在大多数语言中,内部例程可以“特殊”访问原本不可见的本地数据类型和变量。
uinbv5nw3#
修改后的版本有一个问题:它引用了
tree
,这是main方法的一个参数。这是嵌套过程可以实现的一件事:它们可以访问到目前为止已声明的外部作用域中的任何变量。话虽如此,许多开发人员发现嵌套过程是一种混乱的编码风格,并倾向于避免它;他们通常会像您一样重写它,但会将
tree
作为另一个参数添加到ProcessNode
中。mccptt674#
早在将OOP添加到Delphi中之前,Delphi中就已经有了嵌套的过程/函数。这一切都发生在大约25年前。在那些时候,函数中的局部函数有助于保持全局范围的整洁和相关代码之间的更紧密联系。Borland/InEnterprises/Embarcadero从来没有放弃过这一功能,当然,因为否则他们会造成巨大的不兼容性。所以,如果它对你有意义,就使用它,否则就顺其自然。
w6mmgewl5#
嵌套过程和函数(参见Nested_function in wikipedia)用于信息隐藏和局部变量与嵌套函数的私有共享。请看下面的维基百科文章中的语录。受质疑的代码似乎没有使用共享局部变量,但似乎选择将它们作为参数传递。通过嵌套该过程,它向读取器传达其他外部代码访问该嵌套代码的信息。这在“修改后的代码”中并不明确。
词法嵌套函数定义是信息隐藏的一种形式,对于将过程性任务划分为仅在本地有意义的子任务非常有用。这避免了程序的其他部分被与这些部分无关的函数和变量搞得混乱不堪。
在带有嵌套函数的语言中,函数通常还可以包含局部常量和类型(除了局部变量、参数和函数之外),它们以相同的嵌套方式在任何深度以相同的方式封装和隐藏。这可能会进一步增强代码结构化的可能性。