Подтвердить что ты не робот

Процедура внутри процедуры?

Я нашел этот код онлайн, который имеет процедуру внутри процедуры. Я не могу понять, почему автор решил написать так. Я замечаю, что выполняется рекурсивная функция.

Почему он не отделил процедуры, как большинство кодов, которые я видел.

Его реализация:

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 *)
4b9b3361

Ответ 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 никогда не отбрасывали эту функцию, конечно, потому что иначе они создали бы огромную несовместимость. Поэтому используйте его, если это имеет смысл для вас, иначе просто пусть это будет.