{

Delphi同迭代子模式(Iterator模式)

作者 陈省

 

在现实生活中,很多事物都是聚合出现的,比如一般的公司和企业除了皮包公司外,都会由很多的人组成,同时针对这一群人,有都会有一些共同的操作,最常见的就是发工资,发奖金了。我曾经在国企待过一段时间,我所在的国企,工资、奖金计算公式非常复杂,整个工资的计算公式参数就有7、8个,细节已经记得不是很清楚了。但是还记得比较清楚的是,新来不到一年的员工,没有绩效奖金,领导有手机报销费、以及招待费,一般员工则没有。根据这种情况定义TPeople对象为公司员工列表,而TPerson对象为公司员工类:

 

  //员工列表

  TPeople=class(TObjectList)

  end;

 

  //公司员工

  TPerson=class(TObject)

  private

    FIsLeader: Boolean;

    FNewExployee: Boolean;

    FMoney: Double;

    FName: string;

    procedure SetIsLeader(const Value: Boolean);

    procedure SetMoney(const Value: Double);

    procedure SetName(const Value: string);

    procedure SetNewExployee(const Value: Boolean);

  public

    property Name:string read FName write SetName;//姓名

    property NewExployee:Boolean read FNewExployee write SetNewExployee;//是否是新员工

    property IsLeader:Boolean read FIsLeader write SetIsLeader;//是否是领导

    property Money:Double read FMoney write SetMoney;//收入的钱

  end;

 

其中公司员工类有名称,是否是新雇员,是否是领导等属性。而对于发工资,奖金的过程,需要遍历每个员工来实现,代码示意如下:

 

procedure TForm1.btn1Click(Sender: TObject);

var

  I:Integer;

  Person:TPerson;

begin

  //发绩效奖金

  for I:=0 to FPeople.Count-1 do

  begin

    Person:=TPerson(FPeople.Items[I]);

    //如果不是新员工的话

    if not Person.NewExployee then

      Person.Money:=Person.Money+100;

  end;

end;

 

procedure TForm1.btn2Click(Sender: TObject);

var

  I:Integer;

  Person:TPerson;

begin

  //发手机费

  for I:=0 to FPeople.Count-1 do

  begin

    Person:=TPerson(FPeople.Items[I]);

    //如果是领导的话

    if Person.IsLeader then

      Person.Money:=Person.Money+200;

  end;

end;

 

procedure TForm1.btn3Click(Sender: TObject);

var

  I:Integer;

  Person:TPerson;

begin

  //发招待费

  for I:=0 to FPeople.Count-1 do

  begin

    Person:=TPerson(FPeople.Items[I]);

    //如果是领导的话

    if Person.IsLeader then

      Person.Money:=Person.Money+1000;

  end;

end;

 

可以看出来上面的代码大同小异,都需要遍历员工列表容器,找到复合条件的员工,然后对其执行发钱的操作,80%的代码都是相同的,想像一下如果写100多个类似的遍历方法,肯定是非常乏味的。

 

使用迭代器模式可以帮助我们解决这个问题,这一模式的主要的特点就是将对聚合容器对象的元素的遍历访问从聚合对象中抽离出来并放在一个迭代器对象中。客户间接地使用迭代器对聚合对象进行访问,可以不用在关心遍历的方法了。在C++和Java以及最新的C#中都提供了容器类的迭代器,可惜Delphi没有提供,因此我们需要自己来实现迭代器对象。

 

迭代子分为被动模式的迭代子和主动模式的迭代子,被动模式的迭代子是由迭代子对象来控制遍历的过程,而主动模式的迭代子是由客户来控制迭代的过程。一般来说,被动模式的迭代子更容易使用,因为已经定义好了遍历方法。

 

一般来说,迭代子的接口方法至少包含First,Next方法用于定位当前位置,HasNext方法用于标识是否遍历是否已经结束,而CurrentItem则返回当前的元素,有时对于被动迭代子为了简化可以不定义这些方法。下面中IPassiveIterator是一个被动模式的迭代子的接口,其中定义了Iterate方法来遍历每一个符合条件的员工,比如针对老员工的遍历,可以定义一个TOldEmployeeIterator来过滤掉不满足要求的新员工,而TLeaderIterator则可以过滤掉所有的群众。下图是建模后的UML图:

 

 

下面是Iterator的实现代码:

 

  //要执行的操作

  TTask=procedure (APerson:TPerson) of object;

  //被动迭代器接口

  IPassiveIterator=interface

  ['{AF8EE210-D3DA-4AD6-9CDA-1B6F46BE80D6}']

    procedure Iterate(ATask:TTask);

  end;

 

  //过滤新员工

  TOldEmployeeIterator=class(TInterfacedObject, IPassiveIterator)

  private

    FPeople:TPeople;

  public

    constructor Create(APeople:TPeople);

    procedure Iterate(ATask:TTask);

  end;

 

  //过滤领导

  TLeaderIterator=class(TInterfacedObject, IPassiveIterator)

  private

    FPeople:TPeople;

  public

    constructor Create(APeople:TPeople);

    procedure Iterate(ATask:TTask);

  end;

 

….

constructor TOldEmployeeIterator.Create(APeople: TPeople);

begin

  FPeople:=APeople;

end;

 

procedure TOldEmployeeIterator.Iterate(ATask: TTask);

var

  I:Integer;

  APerson:TPerson;

begin

  for I:=0 to FPeople.Count-1 do

  begin

    APerson:=TPerson(FPeople.Items[I]);

    if not APerson.NewExployee then

      ATask(APerson);

  end;

end;

 

{ TLeaderIterator }

 

constructor TLeaderIterator.Create(APeople: TPeople);

begin

  FPeople:=APeople;

end;

 

procedure TLeaderIterator.Iterate(ATask: TTask);

var

  I:Integer;

  APerson:TPerson;

begin

  for I:=0 to FPeople.Count-1 do

  begin

    APerson:=TPerson(FPeople.Items[I]);

    if APerson.IsLeader then

      ATask(APerson);

  end;

end;

 

procedure TForm1.btn4Click(Sender: TObject);

begin

  //发绩效奖金

  with TOldEmployeeIterator.Create(FPeople) do

    Iterate(bonus1);

end;

 

procedure TForm1.bonus1(APerson: TPerson);

begin

  //发绩效奖金

  APerson.Money:=APerson.Money+100;

end;

 

procedure TForm1.bonus2(APerson: TPerson);

begin

  //发手机费

  APerson.Money:=APerson.Money+200;

end;

 

procedure TForm1.bonus3(APerson: TPerson);

begin

  //发招待费

  APerson.Money:=APerson.Money+1000;

end;

 

procedure TForm1.btn5Click(Sender: TObject);

begin

  //发手机费

  with TLeaderIterator.Create(FPeople) do

    Iterate(bonus2);

end;

 

procedure TForm1.btn6Click(Sender: TObject);

begin

  //发招待费

  with TLeaderIterator.Create(FPeople) do

    Iterate(bonus3);

end;

 

使用被动迭代子模型,迭代方法的Iterate需要将操作TTask传入到迭代过程中,可以看出,使用了迭代子模型后,发奖金的代码减少到了两行,比原来少了很多,还要注意的是,这里我创建了TLeaderIterator的实例后并没有释放,这是因为TLeaderIterator等迭代器都是从TInterfacedObject继承来的,Delphi会对TInterfacedObject自动进行引用计数,在函数调用结束后,会自动进行释放,比较类似于C++中的智能指针。

 

除了被动迭代子外,也可以定义主动迭代子来进行迭代,使用主动迭代子,需要客户调用迭代子的Next方法来进行遍历,遍历中如果HasNext为False,则结束遍历。通常来说,主动迭代子更加灵活强大,更容易控制,而被动模式,需要固定TTask的方法类型,不容易改变。

 

主动迭代子还有一个问题就是,迭代的位置被外部化,很难迭代象树这样有分支的复杂的聚合对象,而这时可以考虑使用被动迭代子,因为被动迭代子被递归调用进行遍历时,可以将路径信息保存在堆栈中。

 

多个迭代子的同时进行操作的问题

 

迭代子模式本身允许多个迭代子同时对一个聚合对象进行遍历,但是如果某些迭代子具有增加和删除聚合对象中元素的方法的话,就会造成迭代索引的错误。对于这个问题的解决办法就是使用观察者模式,每个迭代子在创建后注册到聚合对象的对象引用列表中,当聚合对象元素变化后,可以通知迭代子更新位置索引,避免迭代错误。

 

}

本站原创及翻译内容保留版权,欢迎转贴,转贴时请注明转自Delphi深度探索