来自翻译
我承诺在二十年前针对不太流行的编程环境开发的框架上翻译几种材料的原因有两个:
1.几年前,在学习了使用实体框架作为.Net平台的ORM的许多乐趣之后,我徒劳地寻找了Lazarus环境的类似物,并且大体上搜索了freepascal。
令人惊讶的是,她缺少好的ORM。 当时发现的只是一个名为
tiOPF的开源项目,该项目于90年代后期为delphi开发,后来移植到了freepascal。 但是,此框架与大型ORM的通常外观根本不同。
在tiOPF中,没有视觉上的方法来设计对象(在实体中–首先是模型)并将对象映射到关系数据库表中的字段(在实体-中首先是数据库)。 开发人员自己将此事实视为项目的缺点之一,但是,值得一提的是,他针对对象业务模型提供了完整的定位,这仅是一个硬代码而已...
在建议的硬编码级别上,我遇到了问题。 那时,我对框架开发人员完整使用并在文档中每段多次提到的那些范式和方法不是很精通(访问者,链接器,观察者的设计模式,DBMS独立性的多个抽象级别等)。 )。 当时我从事数据库的大型项目完全专注于Lazarus的可视化组件以及可视化环境提供的数据库的工作方式,结果-大量相同的代码:数据库中的三个表具有几乎相同的结构和同质数据,用于查看的三个相同表格,用于编辑的三个相同表格,用于报告的三个相同表格,以及“如何不设计软件”标题顶部的其他所有内容。
阅读了足够的有关数据库和信息系统正确设计原理的文献,包括模板的研究,并且熟悉实体框架,我决定对数据库本身和应用程序进行全面的重构。 如果我完全完成了第一个任务,那么在执行第二个任务时,有两条路要走不同的方向:要么完全学习.net,C#和Entity Framework,要么为熟悉的Lazarus系统找到合适的ORM。 还有第三条,第一条不起眼的自行车道-编写适合您自己需要的ORM,但这不是重点。
框架的源代码评论不多,但是开发人员仍然准备了一定数量的文档(显然是在开发的初期)。 当然,所有这些都是讲英语的,经验表明,尽管有大量的代码,图表和模板编程短语,但是许多说俄语的程序员在英语文档中仍然缺乏导向。 并非总是,并非每个人都希望训练他们理解英语技术文本的能力,而无需将其翻译成俄语。
此外,对译文进行反复校对可让您了解我第一次见到文档时错过的内容,但我并不完全理解或理解不正确。 也就是说,这本身就是一个更好地学习所研究框架的机会。
2.在文档中,作者有意或无意跳过了一些代码,这在他看来可能很明显。 由于其编写的局限性,该文档使用了过时的机制和对象作为示例,已被删除或在新版本的框架中不再使用(我不是说它本身会继续发展吗?)。 另外,当我自己重复开发的示例时,我发现了一些应该修复的错误。 因此,在一些地方,我不仅允许自己翻译文本,还允许我对其进行补充或修订,以使文本保持相关性,并且示例在起作用。
我想从彼得·亨里克森(Peter Henrikson)的一篇文章开始翻译材料,该文章涉及整个框架所代表的第一个“鲸鱼”-访客模板。
原文发表在这里 。
访客和tiOPF模板
本文的目的是介绍Visitor模板,该模板的使用是tiOPF(TechInsite对象持久性框架)框架的主要概念之一。 在使用“访问者”之前,分析其他解决方案之后,我们将详细考虑该问题。 在发展我们自己的访客概念的过程中,我们将面临另一个挑战:需要遍历集合中的所有对象。 这个问题也将被研究。
主要任务是想出一种通用的方法来对集合中的某些对象执行一组相关的方法。 所执行的方法可能会根据对象的内部状态而有所不同。 我们根本不能执行方法,但是可以在同一个对象上执行多个方法。
必要的培训水平
读者应该熟悉对象Pascal,并掌握面向对象编程的基本原理。
本文中的示例业务任务
例如,我们将开发一个通讯录,使您可以创建人员及其联系信息的记录。 随着人与人之间可能的交流方式的增加,应用程序应灵活地允许您添加此类方法而无需进行大量代码处理(我记得一旦完成了添加电话号码的代码处理,我立即需要再次对其进行处理以添加电子邮件)。 我们需要提供两类地址:真实地址,例如家庭住址,邮政,工作地址和电子地址:座机电话,传真,移动电话,电子邮件,网站。
在表示级别上,我们的应用程序应类似于Explorer / Outlook,即应该使用标准组件,例如TreeView和ListView。 该应用程序应该可以快速运行,并且不会给人以庞大的客户端-服务器软件的印象。
应用程序可能看起来像这样:

在树的上下文菜单中,您可以选择添加/删除个人或公司的联系人,然后右键单击联系人数据列表以打开一个对话框来编辑他们,删除或添加数据。
数据可以以各种形式保存,将来我们将考虑如何使用此模板来实现此功能。
开始之前
我们将从对象的简单集合开始-依次具有两个属性的人的列表-名称(名称)和地址(EmailAdrs)。 首先,该列表将在构造函数中填充数据,然后从文件或数据库加载。 当然,这是一个非常简化的示例,但是足以完全实现Visitor模板。
创建一个新的应用程序,并添加主模块的接口部分的两类:TPersonList(从TObjectList继承,并且需要连接使用的contnrs模块)和TPerson(从TObject继承):
TPersonList = class(TObjectList) public constructor Create; end; TPerson = class(TObject) private FEMailAdrs: string; FName: string; public property Name: string read FName write FName; property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end;
在TPersonList构造函数中,我们创建三个TPerson对象并添加到列表中:
constructor TPersonList.Create; var lData: TPerson; begin inherited; lData := TPerson.Create; lData.Name := 'Malcolm Groves'; lData.EMailAdrs := 'malcolm@dontspamme.com';
首先,我们将遍历列表,并对列表的每个元素执行两项操作。 操作类似,但不相同:一个简单的ShowMessage调用以显示TPerson对象的Name和EmailAdrs属性的内容。 在表单中添加两个按钮,并将其命名为:

在表单的首选范围内,还应该添加TPersonList类型的属性(或仅是字段)FPersonList(如果在表单下方声明了该类型,请更改顺序或进行初步的类型声明),然后在onCreate事件处理程序中调用构造函数:
FPersonList := TPersonList.Create;
为了正确释放窗体的onClose事件处理程序中的内存,必须销毁此对象:
FPersonList.Free.
步骤1.硬编码迭代
要显示TPerson对象的名称,请将以下代码添加到第一个按钮的onClick事件处理程序中:
procedure TForm1.Button1Click(Sender: TObject); var i: integer; begin for i := 0 to FPersonList.Count - 1 do ShowMessage(TPerson(FPersonList.Items[i]).Name); end;
对于第二个按钮,处理程序代码如下:
procedure TForm1.Button2Click(Sender: TObject); var i: integer; begin for i := 0 to FPersonList.Count - 1 do ShowMessage(TPerson(FPersonList.Items[i]).EMailAdrs); end;
这是此代码的明显选项:
- 两种方法几乎可以完成相同的操作。 所有区别仅在于它们显示的对象的属性名称;
- 迭代将是乏味的,尤其是当您被迫在代码的一百个地方编写类似的循环时;
- 对TPerson的强硬选择充满了特殊情况。 如果列表中有一个TAnimal实例而没有address属性怎么办? 在此代码中,没有机制可以阻止错误并加以防御。
让我们弄清楚如何通过引入抽象来改进代码:我们将迭代器代码传递给父类。
步骤2.抽象迭代器
因此,我们希望将迭代器逻辑移至基类。 列表迭代器本身非常简单:
for i := 0 to FList.Count - 1 do
听起来我们正在计划使用
Iterator模板。 从《
四方设计模式》一书中可以知道,迭代器可以是内部的,也可以是内部的。 使用外部迭代器时,客户端通过调用Next方法显式控制遍历(例如,TCollection元素的枚举由First,Next,Last方法控制)。 我们将在这里使用内部迭代器,因为通过它的帮助更容易实现树遍历,这是我们的目标。 我们将Iterate方法添加到列表类中,并将回调方法传递给它,该方法必须在列表的每个元素上执行。 对象pascal中的回调被声明为过程类型,例如,我们将拥有TDoSomethingToAPerson。
因此,我们声明了一个过程类型TDoSomethingToAPerson,它采用一个类型为TPerson的参数。 程序类型允许您将方法用作另一个方法的参数,即实现回调。 这样,我们将创建两个方法,一个方法将显示对象的Name属性,另一个方法-EmailAdrs属性,它们本身将作为参数传递给常规迭代器。 最后,类型声明部分应如下所示:
TPerson = class(TObject) private FEMailAdrs: string; FName: string; public property Name: string read FName write FName; property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end; TDoSomethingToAPerson = procedure(const pData: TPerson) of object; TPersonList = class(TObjectList) public constructor Create; procedure DoSomething(pMethod: TDoSomethingToAPerson); end; DoSomething: procedure TPersonList.DoSomething(pMethod: TDoSomethingToAPerson); var i: integer; begin for i := 0 to Count - 1 do pMethod(TPerson(Items[i])); end;
现在,要对列表项执行必要的操作,我们需要做两件事。 首先,使用具有由TDoSomethingToAPerson指定的签名的方法来定义必要的操作,其次,使用指向作为参数传递的这些方法的指针编写DoSomething调用。 在表单描述部分,添加两个声明:
private FPersonList: TPersonList; procedure DoShowName(const pData: TPerson); procedure DoShowEmail(const pData: TPerson);
在这些方法的实现中,我们指出:
procedure TForm1.DoShowName(const pData: TPerson); begin ShowMessage(pData.Name); end; procedure TForm1.DoShowEmail(const pData: TPerson); begin ShowMessage(pData.EMailAdrs); end;
按钮处理程序的代码更改如下:
procedure TForm1.Button1Click(Sender: TObject); begin FPersonList.DoSomething(@DoShowName); end; procedure TForm1.Button2Click(Sender: TObject); begin FPersonList.DoSomething(@DoShowEmail); end;
已经更好了。 现在,我们的代码中具有三个抽象级别。 通用迭代器是实现对象集合的类的方法。 业务逻辑(到目前为止只是通过ShowMessage输出无尽的消息)被分开放置。 在表示(图形界面)级别,业务逻辑在一行中被调用。
很难想象如何用代码替换ShowMessage的调用,该代码使用TQuery对象的SQL查询将TPerson中的数据保存在关系数据库中。 例如,像这样:
procedure TForm1.SavePerson(const pData: TPerson); var lQuery: TQuery; begin lQuery := TQuery.Create(nil); try lQuery.SQL.Text := 'insert into people values (:Name, :EMailAdrs)'; lQuery.ParamByName('Name').AsString := pData.Name; lQuery.ParamByName('EMailAdrs').AsString := pData.EMailAdrs; lQuery.Datababase := gAppDatabase; lQuery.ExecSQL; finally lQuery.Free; end; end;
顺便说一句,这引入了维护数据库连接的新问题。 在我们的请求中,通过某些全局gAppDatabase对象执行与数据库的连接。 但是它将位于何处以及如何工作? 此外,在迭代器的每一步中,我们都会痛苦地创建TQuery对象,配置连接,执行查询,并且不要忘记释放内存。 最好将这些代码包装在一个类中,该类封装了创建和执行SQL查询以及建立和维护与数据库的连接的逻辑。
步骤3.传递对象而不是将指针传递给回调
将对象传递给基类的迭代器方法将解决状态维护的问题。 我们将使用单个Execute方法创建抽象的Visitor类TPersonVisitor,并将对象作为参数传递给此方法。 抽象的Visitor界面如下所示:
TPersonVisitor = class(TObject) public procedure Execute(pPerson: TPerson); virtual; abstract; end;
接下来,将Iterate方法添加到我们的TPersonList类中:
TPersonList = class(TObjectList) public constructor Create; procedure Iterate(pVisitor: TPersonVisitor); end;
此方法的实现如下:
procedure TPersonList.Iterate(pVisitor: TPersonVisitor); var i: integer; begin for i := 0 to Count - 1 do pVisitor.Execute(TPerson(Items[i])); end;
TPersonVisitor类的已实现Visitor的对象被传递给Iterate方法,并且在遍历每个项目的列表项时,将以TPerson实例作为参数调用指定的Visitor(其execute方法)。
让我们创建Visitor的两个实现-TShowNameVisitor和TShowEmailVistor,它们将执行所需的工作。 以下是补充模块接口部分的方法:
TShowNameVisitor = class(TPersonVisitor) public procedure Execute(pPerson: TPerson); override; end; TShowEmailVisitor = class(TPersonVisitor) public procedure Execute(pPerson: TPerson); override; end;
为了简单起见,在它们上执行方法的实现仍将是一行-ShowMessage(pPerson.Name)和ShowMessage(pPerson.EMailAdrs)。
并更改按钮单击处理程序的代码:
procedure TForm1.Button1Click(Sender: TObject); var lVis: TPersonVisitor; begin lVis := TShowNameVisitor.Create; try FPersonList.Iterate(lVis); finally lVis.Free; end; end; procedure TForm1.Button2Click(Sender: TObject); var lVis: TPersonVisitor; begin lVis := TShowEmailVisitor.Create; try FPersonList.Iterate(lVis); finally lVis.Free; end; end;
现在,解决了一个问题,我们为自己创造了另一个。 迭代器逻辑封装在单独的类中; 迭代过程中执行的操作都包装在对象中,这使我们可以保存一些有关状态的信息,但是代码的大小已从一行(FPersonList.DoSomething(@DoShowName))增长到每个按钮处理程序的九行。现在它将为我们提供帮助-这是访客管理器,它将负责创建和释放其副本。可能的是,我们可以提供一些在迭代过程中要执行的对象操作,为此,访客管理器将存储其列表并在每个步骤中进行遍历,您可以 。Olnyaya只有选择的操作接下来将清楚地表明了这种做法的好处,我们将使用游人数据在关系数据库中保存为一个简单的数据保存操作可以通过三种不同的运营商的SQL进行:创建,删除和升级。
步骤4.进一步封装访问者
在继续之前,我们必须封装来访者工作的逻辑,将其与应用程序的业务逻辑分开,以使它不会返回。 为此,将需要三个步骤:创建基类TVisited和TVisitor,然后创建业务对象的基类和业务对象的集合,然后稍微调整我们的特定类TPerson和TPersonList(或TPeople),使它们成为已创建基类的继承人类。 一般而言,类的结构将与以下图表相对应:

TVisitor对象实现两种方法:AcceptVisitor函数和Execute过程,将TVisited类型的对象传递到其中。 TVisited对象又使用TVisitor类型的参数实现Iterate方法。 也就是说,TVisited.Iterate必须在传输的TVisitor对象上调用Execute方法,并发送到其自身实例的链接作为参数,如果实例是一个集合,则将为集合中的每个元素调用Execute方法。 由于我们正在开发通用系统,因此AcceptVisitor功能是必需的。 例如,可以将访问者传递给仅使用TPerson类型进行操作的Visitor,例如TDog类的实例,并且必须具有一种机制来防止由于类型不匹配而引起的异常和访问错误。 TVisited类是TPersistent类的后代,因为稍后我们将需要实现与RTTI使用相关的功能。
模块的接口部分现在将如下所示:
TVisited = class; TVisitor = class(TObject) protected function AcceptVisitor(pVisited: TVisited): boolean; virtual; abstract; public procedure Execute(pVisited: TVisited); virtual; abstract; end; TVisited = class(TPersistent) public procedure Iterate(pVisitor: TVisitor); virtual; end;
TVisitor抽象类的方法将由继承人实现,而TVisited的Iterate方法的一般实现如下所示:
procedure TVisited.Iterate(pVisitor: TVisitor); begin pVisitor.Execute(self); end;
同时,该方法被声明为虚拟方法,因为它有可能在继承人中被覆盖。
步骤5.创建一个共享的业务对象和集合
我们的框架还需要另外两个基类:定义业务对象和此类对象的集合。 将它们称为TtiObject和TtiObjectList。 其中第一个的界面:
TtiObject = class(TVisited) public constructor Create; virtual; end;
在开发过程的后期,我们将使此类复杂化,但是对于当前任务,仅一个虚拟构造函数就可以在继承者中覆盖它。
我们计划从TVisited生成TtiObjectList类,以便在祖先已经实现的方法中使用该行为(还有其他继承原因,将在其位置进行讨论)。 另外,没有什么禁止使用
接口 (接口)而不是抽象类。
TtiObjectList类的接口部分如下:
TtiObjectList = class(TtiObject) private FList: TObjectList; public constructor Create; override; destructor Destroy; override; procedure Clear; procedure Iterate(pVisitor: TVisitor); override; procedure Add(pData: TObject); end;
如您所见,带有对象元素的容器本身位于protected部分中,并且此类客户无法使用。 该类最重要的部分是重写Iterate方法的实现。 如果在基类中该方法简单地称为pVisitor.Execute(self),则此处的实现与枚举列表有关:
procedure TtiObjectList.Iterate(pVisitor: TVisitor); var i: integer; begin inherited Iterate(pVisitor); for i := 0 to FList.Count - 1 do (FList.Items[i] as TVisited).Iterate(pVisitor); end;
其他类方法的实现只需一行代码,而无需考虑自动放置的继承表达式:
Create: FList := TObjectList.Create; Destroy: FList.Free; Clear: if Assigned(FList) then FList.Clear; Add: if Assigned(FList) then FList.Add(pData);
这是整个系统的重要组成部分。 我们有两个基本的业务逻辑类:TtiObject和TtiObjectList。 两者都具有Itisiate方法,将TVisited类的实例传递给该方法。 迭代器本身调用TVisitor类的Execute方法,并将其引用传递给对象本身。 该调用是在继承的顶级类行为中预定义的。 对于容器类,列表中存储的每个对象还具有其Iterate方法,该方法使用TVisitor类型的参数调用,即确保每个特定的Visitor将绕过列表中存储的所有对象以及列表本身作为容器对象。
步骤6.创建访客经理
因此,回到我们自己在第三步中提出的问题。 由于我们不想每次都创建和销毁访问者的副本,因此管理器的开发将是解决方案。 它应该执行两个主要任务:管理访问者列表(已在各个模块的初始化部分中进行了相应注册),并在访问者收到来自客户端的适当命令后运行它们。
为了实现管理器,我们将在模块中添加三个附加类:TVisClassRef,TVisMapping和TtiVisitorManager。
TVisClassRef = class of TVisitor;
TVisClassRef是一种引用类型,它指示特定类的名称-TVisitor的后代。 使用引用类型的含义如下:当调用带有签名的基本Execute方法时
procedure Execute(const pData: TVisited; const pVisClass: TVisClassRef),
在内部,此方法可以使用类似lVisitor:= pVisClass.Create的表达式来创建特定Visitor的实例,而无需先了解其类型。 也就是说,当将其类的名称作为参数传递时,可以在同一Execute方法内动态创建任何类-TVisitor的后代。
第二类TVisMapping是具有两个属性的简单数据结构:对TVisClassRef类型的引用和字符串属性Command。 需要一个类来比较按其名称执行的操作(一个命令,例如“ save”)和这些命令执行的Visitor类。 将其代码添加到项目中:
TVisMapping = class(TObject) private FCommand: string; FVisitorClass: TVisClassRef; public property VisitorClass: TVisClassRef read FVisitorClass write FVisitorClass; property Command: string read FCommand write FCommand; end;
最后一个类是TtiVisitorManager。 当我们使用Manager注册Visitor时,将创建TVisMapping类的实例,该实例输入到Manager列表中。
因此,在管理器中,使用匹配的字符串命令创建了访问者列表,接收到访问者列表后将执行访问者列表。 类接口已添加到模块中:
TtiVisitorManager = class(TObject) private FList: TObjectList; public constructor Create; destructor Destroy; override; procedure RegisterVisitor(const pCommand: string; pVisitorClass: TVisClassRef); procedure Execute(const pCommand: string; pData: TVisited); end;
它的关键方法是RegisterVisitor和Execute。 通常在模块的初始化部分中调用第一个,它描述了Visitor类,并且看起来像这样:
initialization gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowNameVisitor); gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowEMailAdrsVisitor);
该方法本身的代码如下:
procedure TtiVisitorManager.RegisterVisitor(const pCommand: string; pVisitorClass: TVisClassRef); var lData: TVisMapping; begin lData := TVisMapping.Create; lData.Command := pCommand; lData.VisitorClass := pVisitorClass; FList.Add(lData); end;
不难看出,此代码与
Factory模板的Pascal实现非常相似。
另一个重要的Execute方法接受两个参数:用于标识访问者或其身份的命令,以及将调用Iterate方法并带有指向所需访问者实例的链接的数据对象。 Execute方法的完整代码如下:
procedure TtiVisitorManager.Execute(const pCommand: string; pData: TVisited); var i: integer; lVisitor: TVisitor; begin for i := 0 to FList.Count - 1 do if SameText(pCommand, TVisMapping(FList.Items[i]).Command) then begin lVisitor := TVisMapping(FList.Items[i]).VisitorClass.Create; try pData.Iterate(lVisitor); finally lVisitor.Free; end; end; end;
因此,要与一个团队一起运行两个先前注册的访客,我们只需要一行代码:
gTIOPFManager.VisitorManager.Execute('show', FPeople);
接下来,我们将补充我们的项目,以便您可以调用类似的命令:
步骤7.调整业务逻辑类
为TPerson和TPeople业务对象添加TtiObject和TtiObjectList类的祖先,将使我们可以将迭代器逻辑封装在基类中,而不再需要接触它,此外,还可以将带有数据的对象传输到Visitor Manager。
新的容器类声明将如下所示:
TPeople = class(TtiObjectList);
实际上,TPeople类甚至不需要实现任何东西。 从理论上讲,我们可以完全不用TPeople声明,而是将对象存储在TtiObjectList类的实例中,但是由于我们计划编写仅处理TPeople实例的Visitor,因此需要此类。 在AcceptVisitor函数中,将执行以下检查:
Result := pVisited is TPeople.
对于TPerson类,我们添加TtiObject祖先,并将两个现有属性移至已发布的范围,因为将来我们将需要通过RTTI使用这些属性。 正是在此之后,它将大大减少映射关系数据库中的对象和记录所涉及的代码:
TPerson = class(TtiObject) private FEMailAdrs: string; FName: string; published property Name: string read FName write FName; property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end;
步骤8.创建一个原型视图
备注 。 在原始文章中,GUI基于tiOPF作者为方便使用其delphi框架而制作的组件。 这些是DB Aware组件的类似物,它们是标准控件,例如标签,输入字段,复选框,列表等,但是与tiObject对象的某些属性相关联,其方式与将数据显示组件与数据库表中的字段相关联的方式相同。 随着时间的流逝,框架的作者用这些视觉组件将软件包标记为过时和不受欢迎的。 (Mediator). . , , GUI .
1 « show», 2 , « save». memo- .
, «show»:
—
TShowVisitor = class(TVisitor) protected function AcceptVisitor(pVisited: TVisited): boolean; override; public procedure Execute(pVisited: TVisited); override; end;
—
function TShowVisitor.AcceptVisitor(pVisited: TVisited): boolean; begin Result := (pVisited is TPerson); end; procedure TShowVisitor.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then exit; Form1.Memo1.Lines.Add(TPerson(pVisited).Name + ': ' + TPerson(pVisited).EMailAdrs); end;
AcceptVisitor , TPerson, . , .
. private : FPeople TPeople VM TtiVisitorManager. , «show»:
FPeople := TPeople.Create; FillPeople; VM := TtiVisitorManager.Create; VM.RegisterVisitor('show',TShowVisitor);
FilPeople — , , . . FPeople.Free VM.Free.
— ! — :
Memo1.Clear; VM.Execute('show',FPeople);
, . . .
9. ,
, . : ( AssignFile ReadLn), (TStringStream TFileStream) TStringList.
, , . , , . TStringList, — LoadFromFile SaveToFile. , , .
TVisFile:
TVisFile = class(TVisitor) protected FList: TStringList; FFileName: TFileName; public constructor Create; virtual; destructor Destroy; override; end;
:
constructor TVisFile.Create; begin inherited Create; FList := TStringList.Create; if FileExists(FFileName) then FList.LoadFromFile(FFileName); end; destructor TVisFile.Destroy; begin FList.SaveToFile(FFileName); FList.Free; inherited; end;
FFileName ( , , !). , , :

TVisFile: TVisTXTFile TVisCSVFile. *.csv, (), — , . :
constructor TVisCSVFile.Create; begin FFileName := 'contacts.csv'; inherited Create; end; constructor TVisTXTFile.Create; begin FFileName := 'contacts.txt'; inherited Create; end.
10. -
, , . AcceptVisitor Execute. AcceptVisitor , TPeople:
Result := pVisited is TPeople;
execute :
procedure TVisTXtRead.Execute(pVisited: TVisited); var i: integer; lData: TPerson; begin if not AcceptVisitor(pVisited) then Exit;
TPeople, , TStringList, , TPerson TPeople. name emailadrs .
. () TStringList (.. FList.Clear; inherited), AcceptVisitor , TPerson, , . , — , StringList . , , , . SQL , (, ). , :
Result := pVisited is Tperson;
execute StringList , : name , 20 , emaiadrs:
procedure TVisTXTSave.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then exit; FList.Add(PadRight(TPerson(pVisited).Name,20)+PadRight(TPerson(pVisited).EMailAdrs,60)); end;
11. - CSV-
TXT : CSV . ExtractDelimited strutils, :
procedure TVisCSVRead.Execute(pVisited: TVisited); var i: integer; lData: TPerson; begin if not AcceptVisitor(pVisited) then exit; TPeople(pVisited).Clear; for i := 0 to FList.Count - 1 do begin lData := TPerson.Create; lData.Name := ExtractDelimited(1, FList.Strings[i], [',']); lData.EMailAdrs := ExtractDelimited(2, FList.Strings[i], [',']); TPeople(pVisited).Add(lData); end; end; procedure TVisCSVSave.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then exit; FList.Add(TPerson(pVisited).Name + ',' + TPerson(pVisited).EMailAdrs); end;
, , . :
VM.RegisterVisitor('readTXT', TVisTXTRead); VM.RegisterVisitor('saveTXT',TVisTXTSave); VM.RegisterVisitor('readCSV',TVisCSVRead); VM.RegisterVisitor('saveCSV',TVisCSVSave);
:

procedure TForm1.ReadCSVbtnClick(Sender: TObject); begin VM.Execute('readCSV', FPeople); end; procedure TForm1.ReadTXTbtnClick(Sender: TObject); begin VM.Execute('readTXT', FPeople); end; procedure TForm1.SaveCSVbtnClick(Sender: TObject); begin VM.Execute('saveCSV', FPeople); end; procedure TForm1.SaveTXTbtnClick(Sender: TObject); begin VM.Execute('saveTXT', FPeople); end;
. : -, saveTXT saveCSV. save, , .
12.
, , . , ( uses ):
|
|
|
tivisitor.pas
|
| TVisitor TVisited TVisMapping TtiVisitorManager
|
tiobject.pas
| -
| TtiObject TtiObjectList
|
people_BOM.pas
| -
| TPerson TPeople
|
people_SRV.pas
| ,
| TVisFile TVisTXTFile TVisCSVFile TVisCSVSave TVisCSVRead TVisTXTSave TVisTXTRead
|
结论
, . , GoF, . . , , .
—