2012-05-20 13 views
18

Encontré este código en línea que tiene un procedimiento dentro de un procedimiento. No puedo entender por qué el autor habría elegido escribirlo de esta manera. Lo que sí noto es una función recursiva que se está ejecutando.Procedimiento dentro de un procedimiento?

¿Por qué no separó los procedimientos como la mayoría del código que he visto?

Su aplicación:

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

o modificado aplicación:

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

Esta pregunta se reduce a "¿Por qué Pascal es como Pascal, y no como C?". :-) Sin embargo, no voy a votar para cerrar, ya que es una pregunta legítima de un nuevo usuario. –

+2

¿Por qué declararías variables en tu procedimiento en lugar de convertirlas en globales? Uso este razonamiento para usar a veces procedimientos en mis procedimientos, para no tener otro global. –

Respuesta

16

Los procedimientos anidados como ese tienen sentido en este código XML relacionado. Para procesar todos los nodos, se necesita una llamada recursiva de ProcessNode. Debe tener en cuenta que, en ocasiones, las funciones internas necesitan acceder a muchos más datos que unos pocos parámetros.

implementaciones potenciales pueden ser:

  • Uso procedimientos "planos", como en su aplicación;
  • Utilice procedimientos "anidados", como en la implementación original;
  • Cree un class dedicado (o record + métodos) que seguirá siendo privado en la parte implementation de la unidad.

Por supuesto, la tercera opción suena más fácil de mantener.Permitirá una separación clara del proceso, y permiten el uso de variables locales a sus métodos. El uso de un record (o un object para versiones anteriores de Delphi) permitirá que el objeto de procesamiento se asigne en la pila del procedimiento principal, por lo que no necesitará escribir Obj := TInterType.Create; try .. finally Obj.Free. Pero si utiliza un object tenga en cuenta que alguna nueva versión de Delphi has compilation issue - debería utilizar mejor record con métodos.

El estilo de procedimiento "plano" no es mejor que el procedimiento "anidado", y lo que es peor, ya que necesitaría agregar parámetros adicionales a las llamadas internas, o usar algunas variables globales. Por cierto, tener muchas variables para cada llamada aumentará el espacio de la pila y reducirá la velocidad.

El estilo "anidado" está de hecho orientada a OOP. Cuando se llama a una función interna, el compilador pasa la base de la pila de llamadas en un registro a la función anidada (al igual que el parámetro adicional self de un objeto). De modo que la función interna puede acceder a todas las variables de la pila de llamadas, como si estuvieran declaradas en un objeto privado (la 3ª solución).

El Delphi IDE y el depurador interno manejan los procedimientos anidados bastante bien. En mi humilde opinión, podría tener sentido para un pequeño fragmento de código (es decir, algo que pueda leerse en la misma altura de pantalla). Luego, cuando necesite más proceso, un record/object dedicado con métodos y variables explícitas será más fácil de mantener. Pero la opción "plana" no está codificada en mi humilde opinión.

acabo written a blog article about these implementation patterns, que presentará un código fuente de una aplicación QuickSort, que utilizará tan poco espacio de pila como sea posible, y evitará una llamada a un procedimiento anidado dentro de un procedimiento, y utilizar un dedicado privado object en su lugar.

En todos los casos, no tenga miedo de crear algunos objetos/clases internas para implementar sus algoritmos. Las últimas versiones de Delphi permiten incluso tipos privados en la definición class, pero a veces me siento más cómodo haciendo que el objeto interno sea totalmente privado para la parte implementation de la unidad, es decir, que ni siquiera aparecen como miembros privados de la unidad interface.

Las clases no son solo para publicar su proceso fuera de la unidad: OOP se aplica también a implementación patrones. Su código será más fácil de mantener, y en la mayoría de los casos, el parámetro self se usará para referirse a todos los datos asociados a la vez, por lo que su código también puede ser aún más rápido y más ligero.

4

Hay un problema con tu versión revisada: se hace referencia a tree, que es un parámetro para el método principal. Eso es algo que se puede lograr con procedimientos anidados: pueden acceder a cualquier variable desde ámbitos externos que se haya declarado hasta ahora.

Habiendo dicho eso, muchos desarrolladores encuentran que los procedimientos anidados son un estilo de codificación desordenado y prefieren evitarlo; generalmente lo reescribían como lo hizo, pero agrega tree como otro parámetro al ProcessNode.

+0

+1 No encuentro complicados los procedimientos anidados, a menos que sean largos, ¡un pequeño procedimiento recursivo vale un billón de dólares! (: – ComputerSaysNo

+3

A algunas personas les gusta esconder cosas que no deberían llamarse fuera del contexto para el que fueron diseñadas.Este tipo de "ocultación" no solo se encuentra en los objetos (con privado/protegido) también es un motivo para los procedimientos anidados, y es la razón (programación de la estructura) que Wirth puso la característica en el lenguaje en primer lugar. Uno puede lograr la mayor parte de ese propósito haciendo una función no local que solo se usa y declara en la sección de implementación, y luego, todo lo que pierde es la capacidad de compartir alcance local. –

11

La codificación con procedimientos internos como ese es una cuestión de estilo. Se podría argumentar que es "más limpio" ... en el mismo sentido de encapsular todos los datos relacionados y las rutinas dentro de una cosa que uno escucha para "programación orientada a objetos" ... pero también tiene desventajas: más difícil para programar correctamente al principio, más difícil de probar, más difícil de entender para muchos programadores (y por lo tanto posiblemente menos sostenible).

La definición de un procedimiento interno evita que futuros programadores llamen accidentalmente al procedimiento interno y esperan que haga algo razonable. Ese procedimiento interno ni siquiera está definido, y por lo tanto no se puede llamar, en un nivel externo/global.

Definir un procedimiento interno también significa menos posibilidades de una colisión de nombre en el espacio de nombre externo/global, ya que la rutina interna no contribuye con nada a ese espacio de nombres. (Este es un excelente ejemplo: ¿cuántas cosas diferentes todas llamadas "ProcessNode (...)" son posibles?)

Y como se señala, en la mayoría de los idiomas la rutina interna tiene acceso "especial" a lo que de otro modo sería invisible tipos de datos locales y variables.

1

Los procedimientos/funciones anidados han estado disponibles en Delphi mucho antes de que se haya agregado OOP. Eso sucedió hace alrededor de 25 años. En aquellos tiempos, las funciones locales dentro de una función ayudaban a mantener un alcance global más limpio y el código relacionado más cerca. Borland/Inprise/Embarcadero nunca abandonaron esa característica, por supuesto, porque de lo contrario habrían creado una gran incompatibilidad. Así que úselo si tiene sentido para usted, de lo contrario, simplemente déjelo.

+0

Intente formatear una respuesta para que sea más legible. – Naddy

Cuestiones relacionadas