递归属性(Delphi)

3

我正在编写一个应用程序,它正在映射需要保存有关每个文件尽可能多信息的目录结构,以便最终用户可以应用自定义过滤器来操作文件和文件夹。该类还提供基本信息返回到UI,以向最终用户提供有关涉及多少数据的想法。我试图通过属性递归到类的子对象中,以获取所需的信息,这样我只需访问顶级类即可获得所需的信息,而不必担心任何数量的子类中的信息。

type 
  TSomeClass = class(TObject)
  private    
    FContainerForSubObjects: TObjectList<TSomeClass>;
    FSomethingToBeCounted: Integer;
    FHasTheDataChanged: Boolean;
  private
    function GetSomethingToBeCounted: Integer;
    function GetHasTheDataChanged: Boolean;
  public
    property SomethingToBeCounted: Integer read GetSomethingToBeCounted;
    property HasTheDataChanged: Boolean read GetHasTheDataChanged;

为了让类声明更容易理解,请按照以下方式查看。如果您查看硬盘驱动器,则ContainerForSubObjects包含一个文件夹,因此,如果那里有名为data的文件夹,则会有一个表示C:\ Data文件夹的子对象,并且SomethingToBeCounted将是每个文件夹中文件的数量。
现在我想在GetSomethingToBeCounted函数中执行以下操作:
1. 查看是否有子对象(这很容易)。 2. 如果有子对象,请查询其SomethingToBeCounted属性,这将调用GetSomethingToBeCounted函数以返回类 B FSomethingToBeCounted字段的值,但是该函数应根据类的HasTheDataChanged状态进行操作。这就是我希望递归发挥作用的地方 - 如果任何包含的子对象的HasTheDataChanged属性设置为false,则其数据已经更新,然后应返回其值,不需要进行更多处理。 - 如果HasTheDataChanged属性设置为true,则数据不是最新的,应重新计算并返回新重新计算的值。还应设置相应的HasTheDataChanged状态,以便减少进一步的重新处理。 3. 我还假设必须对HasTheDataChanged属性执行类似的传播,以便如果树中某个地方的值发生更改,则所有父对象都将相应地更新。
希望这些要求有意义。
现在是问题的核心。首先,我的想法是否正确,只需访问子对象的属性即可将正确的值传播到根对象,因此无需搜索每个子对象的成千上万行代码。或者,我正在尝试重新发明轮子,并且可以使用已经存在的类而不是制作自己的类。最后但并非最不重要的是,这是否是最有效的方法?

1
嗯,这不是传播(从子节点到父节点的冒泡),而是通常的根到分支的递归轮询。让我有一个树a->b->c。所有的HasTheDataChanged都为false。现在我改变了c的一些东西。根据你的问题,我们会得到a.HasTheDataChanged = b.HasTheDataChanged = false。c.HasTheDataChanged = true。你的轮询函数将会在一个节点上退出,它甚至不会查看b,也不会查看c。 - undefined
也许不使用递归,而是用TStack / TStack<T>数据存储来创建一个循环会更好。只要没有具有不同虚拟实现主轮询函数的不同类。 - undefined
我真的不认为使用一个带有 TStack 对象的循环会真正起作用,因为它与已经使用的 TObjectList 类似。最好的办法是,就像我说的那样,给类添加一个额外的 owner 或 parent 属性,然后通过树向后遍历并设置所需的属性,如果编码正确,应该会触发一直到根节点的更新。 - undefined
@Chris - 如果你能完全掌握从分支到根的传播,那是可以的。 但是你最初的方法有点混合了传播和轮询。这也没关系。例如,你可以引入一种DIRTY标志,但只在确实需要时推迟对新状态的重新计算。例如,让我们在GUI TreeView中显示你的数据。 让我们根据节点的属性为节点分配不同的图标、颜色、渐变或其他生成的背景等。 - undefined
...会重新查询1/2/3/*的状态,然后是1/2/,最后是1/ - 对每个子节点都进行查询。为了批量更新10个子节点,需要进行400次数据获取。你应该通过一种类似于BeginUpdate/EndUpdate的方式来阻止计算,或者只传播简单的标志,如DIRTY,并尽快取消传播。而真正的重新计算仍然按需在自上而下的轮询方式中进行。对于这种轮询,使用堆而不是栈仍然是有意义的 :-) - undefined
显示剩余3条评论
2个回答

3
对于这样的情况,我可能会采取另一种方法。而不是让父对象试图弄清楚哪些子对象已更改,我会让子对象在更改发生时通知其父对象,然后通知其父对象,以此类推。让信息向上冒泡,而不是向下搜索。这将把大部分工作放在引发更改的活动上,并使搜索更快,因为所有信息都预先缓存在树中,而无需寻找它。
type  
  TSomeClass = class(TObject) 
  private     
    FParent: TSomeClass; 
    FSubObjects: TObjectList<TSomeClass>; 
    FSomethingToBeCounted: Integer; 
    FHasTheDataChanged: Boolean; 
    procedure Changed; 
  protected
    procedure SubObjectChanged(ASubObject: TSomeClass); 
  public 
    constructor Create(AParent: TSomeClass = nil);
    destructor Destroy; override;
    procedure DoSomethingToMakeAChange;
    property SomethingToBeCounted: Integer read FSomethingToBeCounted; 
    property HasTheDataChanged: Boolean read FHasTheDataChanged; 
  end;

constructor TSomeObject.Create(AParent: TSomeClass = nil);
begin
  inherited Create;
  FParent := AParent;
  FSubObjects := TObjectList<TSomeClass>.Create;
  //...
end;

destructor TSomeClass.Destroy;
begin
  //...
  FSubObjects.Free;
  inherited Destroy;
end;

procedure TSomeObject.DoSomethingToMakeAChange;
begin
  // update FSomethingToBeCounted as needed
  Changed;
end;

procedure TSomeClass.SubObjectChanged(ASubObject: TSomeClass); 
begin
  // update FSomethingToBeCounted as needed, based on which child was changed
  Changed;
end;

procedure TSomeClass.Changed; 
begin
  FHasTheDataChanged := True; 
  if FParent <> nil then
    FParent.SubObjectChanged(Self); 
end;

+1;关于原则,我认为OP在问题中提到了这种方式,就像值会向上传播到根对象 - undefined

2

我还没有遇到一个现有的组件能够满足你的要求,所以编写自己的组件似乎是可行的。

你的方法可以工作,并且这是我自己多次使用的方法。你有一个递归数据结构,因为TSomeClass包含进一步的TSomeClass对象列表。以其他方式遍历此结构将更加复杂。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接