21

プロシージャ内にプロシージャがあるこのコードをオンラインで見つけました。なぜ作者がこのように書くことを選んだのか理解できません。私が気付いたのは、実行されている再帰関数です。

なぜ彼は私が見たほとんどのコードのように手順を分離しなかったのですか。

彼の実装:

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

4 に答える 4

19

このようなネストされたプロシージャは、このXML関連のコードでは意味があります。すべてのノードを処理するには、の再帰呼び出しProcessNode必要です。内部関数は、いくつかのパラメータよりもはるかに多くのデータにアクセスする必要がある場合があることに注意する必要があります。

潜在的な実装は次のとおりです。

  • 実装の場合と同様に、「フラット」な手順を使用します。
  • 元の実装と同様に、「ネストされた」手順を使用します。
  • ユニットの一部にプライベートのままである専用class(または+メソッド)を作成します。recordimplementation

もちろん、3番目のオプションはより保守しやすいように聞こえます。これにより、プロセスを明確に分離し、メソッドにローカルな変数を使用できるようになりますrecord(または古いバージョンのDelphiの場合)を使用objectすると、処理オブジェクトをメインプロシージャのスタックに割り当てることができるため、を記述する必要はありませんObj := TInterType.Create; try .. finally Obj.Free。ただし、使用する場合はobject、Delphiの一部の新しいバージョンにコンパイルの問題があることに注意してください。メソッドで使用recordする方が適切です。

「フラット」プロシージャスタイルは、「ネストされた」プロシージャよりも優れているわけではなく、さらに悪いことに、内部呼び出しにパラメータを追加したり、いくつかのグローバル変数を使用したりする必要があるためです。ちなみに、呼び出しごとに多くの変数があると、スタックスペースが増え、速度が低下します。

「ネストされた」スタイルは、実際にはOOP指向です。内部関数が呼び出されると、コンパイラはレジスタ内の呼び出し元スタックベースをネストされた関数に渡します(selfオブジェクトの追加パラメータと同様)。したがって、内部関数は、プライベートオブジェクトで宣言されているかのように、すべての呼び出し元スタック変数にアクセスできます(3番目のソリューション)。

Delphi IDEと内部デバッガーは、ネストされたプロシージャを非常にうまく処理します。私見では、いくつかの小さなコード(つまり、同じ画面の高さで読み取ることができるもの)には意味があります。そうすれば、より多くのプロセスが必要になったときに、専用record/objectのメソッドと明示的な変数がより保守しやすくなります。しかし、「フラット」オプションは、コーディングされるべきではない私見です。

これらの実装パターンに関するブログ記事を書いたところです。これは、 QuickSort実装のソースコードを示し、スタックスペースをできるだけ少なくし、プロシージャ内のネストされたプロシージャの呼び出しを回避し、object代わりに専用のプライベート。

いずれの場合も、アルゴリズムを実装するための内部オブジェクト/クラスを作成することを恐れないでください。最新バージョンのDelphiでは、定義でプライベートタイプも使用できますが、内部オブジェクトをユニットの一部に対して完全にプライベートにする、つまりユニットの一部のプライベートメンバーとして表示されないようにするclass方が快適な場合があります。implementationinterface

クラスは、ユニットの外部にプロセスを公開することだけを目的としたものではありません。OOPは実装パターンにも適用されます。コードはより保守しやすくなり、ほとんどの場合、selfパラメーターは関連するすべてのデータを一度に参照するために使用されるため、コードはさらに高速で軽量になる可能性があります。

于 2012-05-20T07:40:28.987 に答える
13

そのような内部手続きをコーディングすることは、スタイルの問題です。「よりクリーン」であると主張することもできます... 「オブジェクト指向プログラミング」について聞いたように、関連するすべてのデータとルーチンを1つのものにカプセル化するのと同じ意味で...しかし、欠点もあります。コーディングがより困難です。最初は正しく、テストが難しく、多くのプログラマーが理解するのが難しくなります (したがって、保守性が低下する可能性があります)。

内部プロシージャーを定義すると、将来のプログラマーが誤って内部プロシージャーを呼び出して、適切な処理を期待することを防止できます。その内部プロシージャは、外部/グローバル レベルでは定義されていないため、呼び出すことができません。

内部プロシージャを定義すると、外部/グローバル名前空間で名前が衝突する可能性が低くなります。これは、内部ルーチンがその名前空間に何も寄与しないためです。(これは良い例です。「ProcessNode(...)」という名前のものがいくつありそうですか?)

前述のように、ほとんどの言語では、内部ルーチンは、そうでなければ見えないローカル データ型と変数への「特別な」アクセスを持っています。

于 2012-05-20T05:12:51.857 に答える
4

改訂版には 1 つの問題がありますtree。メイン メソッドへのパラメーターである を参照しています。これは、ネストされたプロシージャで実現できることの 1 つです。これらのプロシージャは、これまでに宣言された外側のスコープから任意の変数にアクセスできます。

そうは言っても、多くの開発者は、ネストされたプロシージャが面倒なコーディング スタイルであることに気づき、それを避けることを好みます。彼らは通常、あなたと同じように書き直しますが、tree別のパラメーターとしてに追加しますProcessNode

于 2012-05-20T04:49:47.813 に答える
3

ネストされたプロシージャ/関数は、OOP が追加されるずっと前から Delphi で使用可能でした。それはすべて約25年前の出来事です。当時、関数内のローカル関数は、グローバル スコープをクリーンにし、関連するコードをより近くに保つのに役立ちました。Borland / Inprise / Embarcadero は、もちろんその機能を削除することはありませんでした。したがって、それが理にかなっている場合はそれを使用し、そうでない場合はそのままにしてください。

于 2013-12-21T19:31:35.840 に答える