Процедура внутри процедуры?
Я нашел этот код онлайн, который имеет процедуру внутри процедуры.
Я не могу понять, почему автор решил написать так.
Я замечаю, что выполняется рекурсивная функция.
Почему он не отделил процедуры, как большинство кодов, которые я видел.
Его реализация:
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 *)
Ответы
Ответ 1
Вложенные процедуры, подобные этому, имеют смысл в этом коде, связанном с XML. Для обработки всех узлов необходим рекурсивный вызов ProcessNode
. Вы должны заметить, что иногда внутренние функции требуют доступа к гораздо большему количеству данных, чем к нескольким параметрам.
Потенциальные реализации могут быть:
- Используйте "плоские" процедуры, как в вашей реализации;
- Используйте "вложенные" процедуры, как в исходной реализации;
- Создайте выделенный
class
(или record
+ метод), который останется закрытым для части implementation
устройства.
Конечно, третий вариант звучит более удобно. Это позволит четко разделить процесс и позволить использовать локальные переменные для их методов. Использование record
(или object
для более старых версий Delphi) позволит выделить объект обработки в стек основной процедуры, поэтому вам не нужно писать Obj := TInterType.Create; try .. finally Obj.Free
. Но если вы используете object
, обратите внимание, что в некоторой новой версии Delphi есть проблема с компиляцией - вам лучше использовать record
с методами.
"Плоский" стиль процедуры - IMHO не лучше, чем "вложенная" процедура, и что еще хуже, так как ему нужно будет добавить дополнительные параметры для внутренних вызовов или использовать некоторые глобальные переменные. Кстати, наличие большого количества переменных для каждого вызова увеличит пространство стека и уменьшит скорость.
"Вложенный" стиль на самом деле ориентирован на ООП. Когда вызывается внутренняя функция, компилятор передает базу стека вызывающего абонента в регистр во вложенную функцию (как и дополнительный параметр self
объекта). Таким образом, внутренняя функция способна получить доступ ко всем переменным стека вызывающего, как если бы они были объявлены в частном объекте (3-е решение).
Delphi IDE и внутренний отладчик достаточно хорошо обрабатывают вложенные процедуры. ИМХО, это может иметь смысл для небольшого фрагмента кода (то есть того, что можно прочитать на одной и той же высоте экрана). Затем, когда вам нужно больше процессов, выделенный record/object
с методами и явными переменными будет более удобным. Но "плоский" вариант ИМХО не кодируется.
Я просто написал статью в блоге об этих шаблонах реализации, в которой будет представлен исходный код реализации QuickSort, который будет использоваться как как можно меньше, и избегать вызова вложенной процедуры внутри процедуры и вместо этого использовать выделенный частный object
.
Во всех случаях не бойтесь создавать некоторые внутренние объекты/классы для реализации ваших алгоритмов. Последние версии Delphi позволяют даже частные типы в определении class
, но иногда я чувствую себя более комфортно с тем, что внутренний объект полностью закрыт для части implementation
устройства, т.е. даже не являясь частным членом interface
часть устройства.
Классы предназначены не только для публикации вашего процесса за пределами единицы: ООП применяется также к шаблонам реализации. Ваш код будет более ремонтопригодным, и в большинстве случаев параметр self
будет использоваться для одновременного обращения ко всем связанным данным, поэтому ваш код может быть еще быстрее и легче!
Ответ 2
Кодирование с внутренними процедурами вроде этого является вопросом стиля. Можно утверждать, что он "чище"... в том же смысле, что и инкапсулирует все связанные данные и подпрограммы внутри одной вещи, как об этом слышит "объектно-ориентированное программирование"... но у него также есть недостатки: сложнее кодировать правильно изначально, сложнее протестировать, сложнее для многих программистов понять (и, возможно, менее обслуживать).
Определение внутренней процедуры позволяет будущим программистам случайно вызвать внутреннюю процедуру и ожидать, что она сделает что-то разумное. Эта внутренняя процедура даже не определена - она не может быть вызвана - на внешнем/глобальном уровне.
Определение внутренней процедуры также означает меньшую вероятность столкновения имен во внешнем/глобальном пространстве имен, поскольку внутренняя подпрограмма не вносит ничего в это пространство имен. (Это отличный пример: сколько разных вещей, которые называются "ProcessNode (...)", вероятно?)
И как уже отмечалось, на большинстве языков внутренняя подпрограмма имеет "специальный" доступ к тому, что иначе было бы невидимыми локальными типами данных и переменными.
Ответ 3
Есть одна проблема с вашей пересмотренной версией: она ссылается на tree
, который является параметром основного метода. Это одна вещь, которая может быть выполнена с помощью вложенных процедур: они могут получить доступ к любой переменной из внешних областей, которые были объявлены до сих пор.
Сказав это, многие разработчики считают, что вложенные процедуры являются беспорядочным стилем кодирования и предпочитают избегать этого; они обычно переписывают его так же, как и вы, но добавьте tree
как еще один параметр в ProcessNode
.
Ответ 4
Вложенные процедуры/функции были доступны в Delphi задолго до того, как OOP был добавлен к нему. Все это произошло около 25 лет назад. В те времена локальные функции внутри функции помогли сблизить глобальную область очистки и связанный с ней код.
Borland/Inprise/Embarcadero никогда не отбрасывали эту функцию, конечно, потому что иначе они создали бы огромную несовместимость.
Поэтому используйте его, если это имеет смысл для вас, иначе просто пусть это будет.