【问题标题】:Implementing TObjectList's sort without copy/paste code在没有复制/粘贴代码的情况下实现 TObjectList 的排序
【发布时间】:2012-10-01 09:53:46
【问题描述】:

我有一个对节点树中的节点进行排序的过程 (VirtualTreeView) 从 FMM4 报告中提取的所有内存泄漏都存储在 TMemoryLeakList 类的对象中(这些是我要排序的列表),这些对象存储在名为 TGroupedMemoryLeakList 的列表列表中,并且 TMLL 和 TGMLL 都扩展了 TObjectList。如果我想保持能够在升序和降序排序之间进行选择以及在四种不同数据类型中的一种排序之间进行选择的功能,我“必须”实现八种不同的比较方法(4 种排序类型 * 2 种排序方向)我继续使用主要的排序方法,因为我的 TMLL 列表扩展了 TObjectList。主要排序方式类似于this

fSortType 和 fSortDirection 字段的值是从 GUI 组合框中获取的。 这八个通用比较函数之一看起来像this。 剩下的七个是这个的复制/粘贴变体。

有没有什么合理的方法可以重构大量的复制粘贴代码,同时仍然保留选择特定排序类型和方向的功能?

【问题讨论】:

  • 在现代 Delphi 中,您可以调用 SortList 而不是 Sort 并传入 reference to 比较函数。这将接受对象的方法或匿名过程。这样你就可以把你的状态带入比较功能。不想使用全局变量。或者您可以使用TObjectList<TMemoryLeak> 并将IComparer<TMemoryLeak> 传递给Sort 函数。您可以再次传递状态。您的目标是什么 Delphi?在我看来,这些解决方案中的任何一个都比使用 Contnrs.TObjectList 的任何解决方案都要好。
  • 我使用的是 rad studio 2007,所以我真的不知道它是否符合现代 Delphi 的要求。一旦我理解它,我会尝试你的建议:)
  • 不,那是古老的德尔福。这种方式没有骰子。类助手可以让生活更轻松,但你现在可能已经得到了你需要的东西。
  • 我建议同时使用 SortList 与 Sort,在我的测试中它似乎快了 3 倍。

标签: delphi sorting refactoring


【解决方案1】:

关于重构的好问题,但我不喜欢您可能正在寻找的答案。几行额外的代码或一些额外的例程并没有错。尤其是后者,在这种情况下,命名有助于提高可读性。

我的建议是:保留原有设计,但缩短代码:

function CompareSizeAsc(Item1, Item2: Pointer): Integer;
begin
  Result := TMemoryLeak(Item2).Size - TMemoryLeak(Item1).Size;
end;

function CompareSizeDesc(Item1, Item2: Pointer): Integer;
begin
  Result := TMemoryLeak(Item1).Size - TMemoryLeak(Item2).Size;
end;

function CompareClassNameAsc(Item1, Item2: Pointer): Integer;
begin
  Result := CompareStr(TMemoryLeak(Item1).ClassName,
    TMemoryLeak(Item2).ClassName);
end;

procedure TMemoryLeakList.Sort;
begin
  case FSortDirection of
    sdAsc:
      case FSortType of
        stSize: inherited Sort(CompareSizeAsc);
        stClassName: inherited Sort(CompareClassNameAsc);
        stCallStackSize: inherited Sort(CompareCallStackSizeAsc);
        stId: inherited Sort(CompareIdAsc);
      end;
    sdDesc:
      case FSortType of
        stSize: inherited Sort(CompareSizeDesc);
        stClassName: inherited Sort(CompareClassNameDesc);
        stCallStackSize: inherited Sort(CompareCallStackSizeDesc);
        stId: inherited Sort(CompareIdDesc);
      end;
  end;
end;

你不能让它比这个小得多,并保持相同级别的可读性。

当然,您可以按照Arioch 'The 的建议重写Sort 例程:

procedure TMemoryLeakList.Sort;
const
  Compares: array[TSortDirection, TSortType] of TListSortCompare =
    ((CompareSizeAsc, CompareClassNameAsc, CompareCallStackSizeAsc,
    CompareIdAsc), (CompareSizeDesc, CompareClassNameDesc,
    CompareCallStackSizeDesc, CompareIdDesc));
begin
  inherited Sort(Compares[FSortDirection, FSortType]);
end;

但是:为什么不重写 QuickSort 例程以消除对单独比较例程的需要?

或者,您可以将所有权添加到 TMemoryLeak,在这种情况下,您可以参考拥有列表及其排序方向和排序类型,以便在单个比较例程中使用。

【讨论】:

    【解决方案2】:

    使用函数指针。

    var comparator1, comparator2: function (Item1, Item2: Pointer): Integer;
    
    function sortComplex (Item1, Item2: Pointer): Integer;
    begin
      Result := comparator1(Item1, Item2);
      if 0 = Result then   Result := comparator2(Item1, Item2);
    end;
    

    那么你的 GUI 元素应该表现得像

     case ListSortType.ItemIndex of
        itemBySzie : comparator1 := sortBySizeProcAsc;
    ....
     end;
    
     DoNewSort;
    

    PS:确保在用户第一次单击任何 GUI 元素之前正确指定这些指针;

    PPS:您可以像

    一样进一步重新排列
     type t_criteria = (bySize, byName,...);
          t_comparators = array[t_criteria] of array [boolean {Descending?}]
                          of function (Item1, Item2: Pointer): Integer;
    
     const comparator1table: t_comparators = 
           ( {bySize} ( {false} sortBySizeProcAsc, {true} sortBySizeProcDesc),
             {byName} ( {false} sortByNameProcAsc, ...
    

    然后,您将从该数组常量中填充工作指针

    【讨论】:

    • +1 为您的明显努力,但它并没有真正使我的代码更短。
    • 它会使其更加解耦和可维护。
    • 我发布了一个解决方案-您介意看看并提出答案吗?
    【解决方案3】:

    这是我的解决方案。除了完全重写这两个过程之外,我还在我的 TMemoryLeakList 类中添加了两个“静态”变量,并删除了以前的同名实例变量。这样,Sort 函数就可以全局访问它们。

    TMemoryLeakList=class(TObjectList)
    class var fSortType      :TMlSortType;
    class var fSortDirection :TMLSortDirection;
    ...
    end
    
    procedure TMemoryLeakList.Sort;
    begin
      inherited sort(sortBySomethingSomething);
    end;
    
    function sortBySomethingSomething(Item1, Item2: Pointer): Integer;
    var
     a, b : string;
     ret : Integer;
    begin
      ret := 1;
      if(TMemoryLeakList.fSortDirection = sdAsc) then
         ret := -1;
      case TMemoryLeakList.fSortType of stSize:
      begin
        a := IntToStr(TMemoryLeak(Item1).Size);
        b := IntToStr(TmemoryLeak(Item2).Size);
      end;
      end;
      case TMemoryLeakList.fSortType of stClassName:
      begin
        a := TMemoryLeak(Item1).ClassName;
        b := TMemoryLeak(Item2).ClassName;
      end;
      end;
      case TMemoryLeakList.fSortType of stID:
      begin
        a := IntToStr(TMemoryLeak(Item1).ID);
        b := IntToStr(TMemoryLeak(Item2).ID);
      end;
      end;
      case TMemoryLeakList.fSortType of stCallStackSize:
      begin
        a := IntToStr(TMemoryLeak(Item1).CallStack.Count);
        b := IntToStr(TMemoryLeak(Item2).CallStack.Count);
      end;
      end;
      //...jos tu
      if a=b then
        Result:=0
      else if a>b then
        Result:=-1*ret
      else if a<b then
        Result:=1*ret;
    end;
    

    我想重写这个解决方案,以便在TMemoryLeakList 中使用实例有界变量fSortType,fSortDirection,但似乎不可能将成员函数传递给继承的排序函数(来自TObjectList),或者是吗?

    【讨论】:

    • 抱歉,这让事情变得更糟了。首先,将所有整数类型值转换为字符串,然后将这些字符串相互比较。转换和字符串比较效率低下,尤其是在这类循环中,您应该希望事情尽可能缩小。回答您的问题:确实,不可能向 TListSortCompare 例程添加第三个参数。
    • @NGLN 这不会按预期工作,比如a := "123"b := "4",desc 排序将产生(b,a) 而不是(a,b)。我有一个问题要问你@NGLN。如果ab 是对象类型怎么办?是否会在运行时确定类型以便调用合适的运算符&lt;,&gt;,=?那么它会要求进行(隐式)转换吗? P.S Delphi 对我来说很冷漠,所以请原谅我在这里假设事情。
    • @iccthedral 如果 'a' 和 'b' 是 TObject 类型,编译器会给出“运算符不适用于此操作数类型”消息,因此不会有隐式转换,我害怕。
    • 我猜它可以与某种变体记录一起使用,相当于C 语言中的union。 Delphi有这种东西吗?
    • @Arioch'在这种情况下,分支预测会不会做得很好?在排序过程中只执行一个分支。顺便说一句,你的解决方案很棒。
    猜你喜欢
    • 2011-06-08
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 2018-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多