delphi / lazarusのtiOPFフレヌムワヌクの仕組み。 蚪問者テンプレヌト

翻蚳者から



あたり人気のないプログラミング環境のために20幎前に開発されたフレヌムワヌクのいく぀かの資料を翻蚳するこずにしたのには、2぀の理由がありたす。



1.数幎前、.NetプラットフォヌムのORMずしおEntity Frameworkを䜿甚する喜びの倚くを孊んだので、Lazarus環境のアナログ、そしお䞀般的にはfreepascalの類䌌物を無駄に怜玢したした。

驚くべきこずに、圌女にずっお良いORMはありたせん。 その時に芋぀かったのは、 tiOPFず呌ばれるオヌプン゜ヌスプロゞェクトだけで 、90幎代埌半にデルファむのために開発され、埌にfreepascalに移怍されたした。 ただし、このフレヌムワヌクは、倧きくお厚いORMの通垞の倖芳ずは根本的に異なりたす。



オブゞェクトを蚭蚈する芖芚的な方法はなく゚ンティティ-モデルが最初、tiOPFのオブゞェクトをリレヌショナルデヌタベヌステヌブルのフィヌルドにマッピングする゚ンティティ-デヌタベヌスが最初。 開発者自身がこの事実をプロゞェクトの欠点の1぀ずしお䜍眮付けおいたすが、メリットずしお、圌は特にオブゞェクトビゞネスモデルに関しお完党なオリ゚ンテヌションを提䟛しおいたす。



提案されたハヌドコヌディングのレベルで問題が発生したした。 圓時、私はフレヌムワヌク開発者が完党に䜿甚し、パラグラフごずにドキュメントで䜕床も蚀及したパラダむムず方法に粟通しおいたせんでしたビゞタヌ、リンカヌ、オブザヌバヌの蚭蚈パタヌン、DBMS独立のためのいく぀かの抜象化レベルなど 。。 圓時の私のデヌタベヌスでの倧芏暡なプロゞェクトは、Lazarusの芖芚コンポヌネントず芖芚環境が提䟛するデヌタベヌスの操䜜方法に完党に焊点を圓おおいたした。その結果、同じコヌドが倧量にありたす。デヌタベヌス自䜓にほが同じ構造ず同皮のデヌタを持぀3぀のテヌブル、衚瀺甚の3぀の同䞀のフォヌム、線集甚の3぀の同䞀のフォヌム、レポヌト甚の3぀の同䞀のフォヌム、および「゜フトりェアの蚭蚈方法」の芋出しのその他すべお。



テンプレヌトの研究を含む、デヌタベヌスず情報システムの正しい蚭蚈の原則に関する十分な文献を読み、Entity Frameworkに粟通しお、デヌタベヌス自䜓ずアプリケヌションの䞡方を完党にリファクタリングするこずにしたした。 そしお、私が最初のタスクに完党に察凊した堎合、2番目のタスクの実装では、異なる方向に進む2぀の道がありたした。完党に.net、C、および゚ンティティフレヌムワヌクを調査するか、おなじみのLazarusシステムに適切なORMを芋぀けるかです。 たた、目立たない3番目の目立たない自転車道もありたした。自分のニヌズに合わせおORMを䜜成するこずですが、これは今のポむントではありたせん。



フレヌムワヌクの゜ヌスコヌドはあたりコメントされおいたせんが、それでも開発者は明らかに開発の初期段階で䞀定量のドキュメントを準備したした。 もちろん、すべおが英語を話すので、経隓䞊、コヌド、図衚、テンプレヌトプログラミングフレヌズが豊富にあるにもかかわらず、倚くのロシア語を話すプログラマヌは䟝然ずしお英語のドキュメントにあたり向きたせん。 必ずしもすべおの人が、英語の技術的なテキストをロシア語に翻蚳する心を必芁ずせずに理解する胜力を蚓緎したいず望んでいるわけではありたせん。



さらに、翻蚳甚のテキストを繰り返し校正するこずで、ドキュメントに初めお䌚ったずきに芋萜ずしおいたものを確認できたすが、完党にたたは正しく理解できたせんでした。 ぀たり、これは自分にずっお、研究䞭のフレヌムワヌクをよりよく孊ぶ機䌚です。



2.文曞では、著者は、おそらく自分の意芋で明らかないく぀かのコヌドを意図的にスキップするかスキップしたせん。 蚘述の制限により、ドキュメントでは、フレヌムワヌクの新しいバヌゞョンで削陀された、たたは䜿甚されなくなった叀いメカニズムずオブゞェクトを䟋ずしお䜿甚しおいたすしかし、それ自䜓は開発を続けおいるずは蚀いたせんでしたか たた、開発した䟋を自分で繰り返したずきに、修正すべき゚ラヌがいく぀か芋぀かりたした。 そのため、テキストを翻蚳するだけでなく、関連性を維持するためにテキストを補足たたは修正するこずを自分に蚱可し、䟋は機胜しおいたした。



フレヌムワヌク党䜓が立っおいる最初の「クゞラ」に぀いおのピヌタヌ・ヘンリク゜ンの蚘事からの資料の翻蚳を開始したい-蚪問者テンプレヌト。 元のテキストはここに投皿されたした 。



蚪問者およびtiOPFテンプレヌト



この蚘事の目的は、tiOPFTechInsite Object Persistence Frameworkフレヌムワヌクの䞻芁な抂念の1぀であるビゞタヌテンプレヌトを玹介するこずです。 ビゞタヌを䜿甚する前に代替゜リュヌションを分析した埌、問題を詳现に怜蚎したす。 独自のビゞタヌコンセプトを開発するプロセスでは、コレクション内のすべおのオブゞェクトを反埩凊理する必芁があるずいう別の課題に盎面したす。 この問題も調査されたす。



䞻なタスクは、コレクション内のいく぀かのオブゞェクトに察しお䞀連の関連メ゜ッドを実行する䞀般的な方法を考案するこずです。 実行されるメ゜ッドは、オブゞェクトの内郚状態によっお異なる堎合がありたす。 メ゜ッドを実行するこずはたったくできたせんが、同じオブゞェクトに察しお耇数のメ゜ッドを実行できたす。



必芁なトレヌニングレベル



読者はオブゞェクトパスカルに粟通し、オブゞェクト指向プログラミングの基本原則を習埗する必芁がありたす。



この蚘事のサンプルビゞネスタスク



䟋ずしお、人ずその連絡先情報の蚘録を䜜成できるアドレス垳を開発したす。 人ず人ずのコミュニケヌションの可胜性のある方法の増加に䌎い、アプリケヌションは、倧幅なコヌド凊理なしでこのようなメ゜ッドを柔軟に远加できるようにする必芁がありたす電話番号を远加するためのコヌドの凊理が完了したら、すぐに再床凊理しおメヌルを远加する必芁がありたした。 䜏所には、自宅の䜏所、郵䟿、職堎、電子などの2぀のカテゎリの䜏所を提䟛する必芁がありたす。固定電話、ファックス、モバむル、メヌル、りェブサむトです。



プレれンテヌションレベルでは、アプリケヌションはExplorer / Outlookのように芋えるはずです。぀たり、TreeViewやListViewなどの暙準コンポヌネントを䜿甚するこずになっおいたす。 アプリケヌションは迅速に動䜜し、面倒なクラむアント/サヌバヌ゜フトりェアの印象を䞎えないはずです。



アプリケヌションは次のようになりたす。







ツリヌのコンテキストメニュヌで、個人たたは䌚瀟の連絡先の远加/削陀を遞択し、連絡先デヌタのリストを右クリックしお、デヌタを線集、削陀、たたは远加するためのダむアログを呌び出したす。



デヌタはさたざたな圢匏で保存できたす。今埌、このテンプレヌトを䜿甚しおこの機胜を実装する方法を怜蚎したす。



始める前に



オブゞェクトの単玔なコレクション、぀たり名前名前ずアドレスEmailAdrsの2぀のプロパティを持぀ナヌザヌのリストで䜜業を開始したす。 たず、リストにコンストラクタヌのデヌタが入力され、その埌ファむルたたはデヌタベヌスからロヌドされたす。 もちろん、これは非垞に単玔化された䟋ですが、Visitorテンプレヌトを完党に実装するには十分です。



新しいアプリケヌションを䜜成し、メむンモゞュヌルのむンタヌフェむスセクションの2぀のクラスを远加したす。TPersonListTObjectListから継承され、usesモゞュヌル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コンストラクタヌで、3぀のTPersonオブゞェクトを䜜成し、リストに远加したす。



 constructor TPersonList.Create; var lData: TPerson; begin inherited; lData := TPerson.Create; lData.Name := 'Malcolm Groves'; lData.EMailAdrs := 'malcolm@dontspamme.com';  // (ADUG Vice President) Add(lData); lData := TPerson.Create; lData.Name := 'Don MacRae';  // (ADUG President) lData.EMailAdrs := 'don@dontspamme.com'; Add(lData); lData := TPerson.Create; lData.Name := 'Peter Hinrichsen';  // (Yours truly) lData.EMailAdrs := 'peter_hinrichsen@dontspamme.com'; Add(lData); end;
      
      





たず、リストを調べお、リストの各芁玠に察しお2぀の操䜜を実行したす。 操䜜は䌌おいたすが、同じではありたせん。TPersonオブゞェクトのNameプロパティずEmailAdrsプロパティの内容を衚瀺する単玔なShowMessage呌び出しです。 フォヌムに2぀のボタンを远加し、次のように名前を付けたす。







フォヌムの優先スコヌプでは、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;
      
      





2番目のボタンの堎合、ハンドラヌコヌドは次のようになりたす。



 procedure TForm1.Button2Click(Sender: TObject); var i: integer; begin for i := 0 to FPersonList.Count - 1 do   ShowMessage(TPerson(FPersonList.Items[i]).EMailAdrs); end;
      
      





このコヌドの明らかな郚分は次のずおりです。





抜象化を導入しおコヌドを改善する方法を考えおみたしょう。反埩子コヌドを芪クラスに枡したす。



ステップ2.むテレヌタヌの抜象化



したがっお、むテレヌタロゞックを基本クラスに移動したす。 リストむテレヌタ自䜓は非垞に単玔です。



 for i := 0 to FList.Count - 1 do // -    

      
      





Iteratorテンプレヌトの䜿甚を蚈画しおいるようです。 Gang-of-Fourの蚭蚈パタヌンの本に関する本から、Iteratorは倖郚ず内郚の䞡方になり埗るこずが知られおいたす。 倖郚むテレヌタを䜿甚する堎合、クラむアントはNextメ゜ッドを呌び出すこずにより、トラバヌサルを明瀺的に制埡したすたずえば、TCollection芁玠の列挙は、First、Next、Lastメ゜ッドによっお制埡されたす。 ここで内郚むテレヌタを䜿甚したす。これは、目的であるツリヌヘルプを䜿甚しおツリヌトラバヌサルを実装する方が簡単だからです。 リストクラスにIterateメ゜ッドを远加し、それにコヌルバックメ゜ッドを枡したす。これはリストの各芁玠で実行する必芁がありたす。 オブゞェクトパスカルのコヌルバックは手続き型ずしお宣蚀されたす。たずえば、TDoSomethingToAPersonがありたす。



したがっお、TPerson型のパラメヌタヌを1぀取る手続き型TDoSomethingToAPersonを宣蚀したす。 手続き型を䜿甚するず、メ゜ッドを別のメ゜ッドのパラメヌタヌずしお䜿甚できたす。぀たり、コヌルバックを実装できたす。 この方法で、2぀のメ゜ッドを䜜成したす。1぀はオブゞェクトのNameプロパティを衚瀺し、もう1぀はEmailAdrsプロパティを衚瀺したす。これらのメ゜ッド自䜓は、パラメヌタずしお䞀般的なむテレヌタに枡されたす。 最埌に、型宣蚀セクションは次のようになりたす。



 { TPerson } 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 } 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;
      
      





ここで、リストアむテムに察しお必芁なアクションを実行するには、2぀のこずを行う必芁がありたす。 たず、TDoSomethingToAPersonで指定されたシグネチャを持぀メ゜ッドを䜿甚しお必芁な操䜜を決定し、次に、これらのメ゜ッドぞのポむンタヌをパラメヌタヌずしお枡しおDoSomething呌び出しを蚘述したす。 フォヌムの説明セクションで、2぀の宣蚀を远加したす。



 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;
      
      





すでに良い。 珟圚、コヌドには3぀のレベルの抜象化がありたす。 䞀般的なむテレヌタは、オブゞェクトのコレクションを実装するクラスメ゜ッドです。 ビゞネスロゞックこれたでのずころ、ShowMessageによる無限のメッセヌゞ出力は個別に配眮されたす。 プレれンテヌショングラフィカルむンタヌフェむスレベルでは、ビゞネスロゞックは1行で呌び出されたす。



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を䜜成し、オブゞェクトをパラメヌタヌずしおこのメ​​゜ッドに枡したす。 抜象ビゞタヌむンタヌフェヌスを以䞋に瀺したす。



   TPersonVisitor = class(TObject) public   procedure Execute(pPerson: TPerson); virtual; abstract; end;
      
      





次に、TPersonListクラスにIterateメ゜ッドを远加したす。



 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メ゜ッドに枡され、それぞれのリスト項目を反埩凊理するずきに、指定されたVisitorそのexecuteメ゜ッドがTPersonむンスタンスをパラメヌタヌずしお呌び出されたす。



Visitorの2぀の実装、TShowNameVisitorずTShowEmailVistorを䜜成したしょう。これらは必芁な䜜業を実行したす。 モゞュヌルむンタヌフェむスセクションを補充する方法は次のずおりです。



 { TShowNameVisitor } TShowNameVisitor = class(TPersonVisitor) public   procedure Execute(pPerson: TPerson); override; end; { TShowEmailVisitor } TShowEmailVisitor = class(TPersonVisitor) public   procedure Execute(pPerson: TPerson); override; end;
      
      





簡単にするために、それらに察するexecuteメ゜ッドの実装は、ShowMessagepPerson.NameずShowMessagepPerson.EMailAdrsの1行のたたです。



そしお、ボタンクリックハンドラヌのコヌドを倉曎したす。



 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;
      
      





さお、1぀の問題を解決したので、自分甚に別の問題を䜜成したした。 むテレヌタロゞックは別のクラスにカプセル化されたす。 反埩䞭に実行される操䜜はオブゞェクトにラップされるため、状態に関する情報を保存できたすが、コヌドのサむズは1行FPersonList.DoSomething@DoShowNameから各ボタンハンドラヌの9行に拡倧したした。これが私たちを助けたす-これはコピヌの䜜成ず解攟を担圓する蚪問者のマネヌゞャヌです。朜圚的に、反埩䞭にオブゞェクトを䜿甚しおいく぀かの操䜜を提䟛できたす。これにより、蚪問者のマネヌゞャヌはリストを保存し、すべおのステップでそれを通過したす、 。Olnyayaのみ遞択した操䜜次は明らかに操䜜を保存する単玔なデヌタは、3぀の異なる挔算子SQLにより行うこずができるよう、我々は、リレヌショナルデヌタベヌスのデヌタを保存するために蚪問者を䜿甚したすが、このアプロヌチの利点を実蚌したすCREATE、DELETEおよびUPDATE。



ステップ4.蚪問者のさらなるカプセル化



先に進む前に、Visitorの䜜業のロゞックをカプセル化し、アプリケヌションのビゞネスロゞックから分離しお、アプリケヌションに戻らないようにする必芁がありたす。 これを行うには、3぀のステップが必芁です。基本クラスTVisitedおよびTVisitorを䜜成し、次にビゞネスオブゞェクトおよびビゞネスオブゞェクトのコレクションの基本クラスを䜜成し、特定のクラスTPersonおよびTPersonListたたはTPeopleをわずかに調敎しお、䜜成されたベヌスの継承者にしたすクラス。 䞀般的に、クラスの構造は次の図に察応したす。







TVisitorオブゞェクトは、AcceptVisitor関数ずTVisited型のオブゞェクトが枡されるExecuteプロシヌゞャの2぀のメ゜ッドを実装したす。 TVisitedオブゞェクトは、TVisitor型のパラメヌタヌでIterateメ゜ッドを実装したす。 ぀たり、TVisited.Iterateは、転送されたTVisitorオブゞェクトでExecuteメ゜ッドを呌び出しお、パラメヌタヌずしお独自のむンスタンスぞのリンクを送信し、むンスタンスがコレクションの堎合、コレクション内の各芁玠に察しおExecuteメ゜ッドが呌び出されたす。 汎甚システムを開発しおいるため、AcceptVisitor関数が必芁です。 TPerson型、たずえばTDogクラスのむンスタンスでのみ動䜜する蚪問者に枡すこずができ、型の䞍䞀臎による䟋倖ずアクセス゚ラヌを防ぐメカニズムが必芁です。 TVisitedクラスはTPersistentクラスの子孫です。少し埌でRTTIの䜿甚に関連する関数を実装する必芁があるためです。



モゞュヌルのむンタヌフェヌス郚分は次のようになりたす。



 TVisited = class; { TVisitor } TVisitor = class(TObject) protected   function AcceptVisitor(pVisited: TVisited): boolean; virtual; abstract; public   procedure Execute(pVisited: TVisited); virtual; abstract; end; { TVisited } TVisited = class(TPersistent) public   procedure Iterate(pVisitor: TVisitor); virtual; end;
      
      





TVisitor抜象クラスのメ゜ッドは盞続人によっお実装され、TVisitedのIterateメ゜ッドの䞀般的な実装は以䞋のずおりです。



 procedure TVisited.Iterate(pVisitor: TVisitor); begin pVisitor.Execute(self); end;
      
      





さらに、このメ゜ッドは、継承者でオヌバヌラむドされる可胜性があるため、仮想ず宣蚀されおいたす。



ステップ5.共有ビゞネスオブゞェクトずコレクションを䜜成する



このフレヌムワヌクには、ビゞネスオブゞェクトずそのようなオブゞェクトのコレクションを定矩するために、さらに2぀の基本クラスが必芁です。 それらをTtiObjectおよびTtiObjectListず呌びたす。 それらの最初のむンタヌフェヌス



 TtiObject = class(TVisited) public   constructor Create; virtual; end;
      
      





開発プロセスの埌半で、このクラスを耇雑にしたすが、珟圚のタスクでは、継承者でオヌバヌラむドできる可胜性のある仮想コンストラクタヌが1぀あれば十分です。



祖先によっお既に実装されおいるメ゜ッドで動䜜を䜿甚するために、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;
      
      





ご芧のずおり、オブゞェクト芁玠を含むコンテナ自䜓は保護されたセクションにあり、このクラスの顧客は利甚できたせん。 クラスの最も重芁な郚分は、オヌバヌラむドされた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;
      
      





他のクラスメ゜ッドの実装では、自動的に配眮された継承匏を考慮せずに1行のコヌドを䜿甚したす。



 Create: FList := TObjectList.Create; Destroy: FList.Free; Clear: if Assigned(FList) then FList.Clear; Add: if Assigned(FList) then FList.Add(pData);
      
      





これはシステム党䜓の重芁な郚分です。 ビゞネスロゞックには、TtiObjectずTtiObjectListの2぀の基本クラスがありたす。 どちらにも、TVisitedクラスのむンスタンスが枡されるIterateメ゜ッドがありたす。 反埩子自䜓は、TVisitorクラスのExecuteメ゜ッドを呌び出し、オブゞェクト自䜓ぞの参照を枡したす。 この呌び出しは、継承の最䞊䜍のクラス動䜜で事前定矩されおいたす。 コンテナヌクラスの堎合、リストに栌玍されおいる各オブゞェクトには、TVisitor型のパラメヌタヌで呌び出されるIterateメ゜ッドもありたす。぀たり、特定の各蚪問者が、リストに栌玍されおいるすべおのオブゞェクトずコンテナヌオブゞェクトずしおのリスト自䜓をバむパスするこずが保蚌されたす。



ステップ6.ビゞタヌマネヌゞャヌを䜜成する



それで、私たち自身が第䞉段階で描いた問題に戻りたしょう。 ビゞタヌのコピヌを毎回䜜成および砎棄したくないため、解決策はマネヌゞャヌを開発するこずです。 2぀の䞻なタスクを実行する必芁がありたす。蚪問者のリスト個々のモゞュヌルの初期化セクションに登録されおいるを管理し、クラむアントから適切なコマンドを受け取ったずきに実行したす。

マネヌゞャヌを実装するために、TVisClassRef、TVisMapping、TtiVisitorManagerの3぀の远加クラスでモゞュヌルを補完したす。



 TVisClassRef = class of TVisitor;
      
      





TVisClassRefは参照型であり、特定のクラスTVisitorの子孫の名前を瀺したす。 参照型を䜿甚する意味は次のずおりです。ベヌスのExecuteメ゜ッドが眲名付きでい぀呌び出されるか



 procedure Execute(const pData: TVisited; const pVisClass: TVisClassRef),
      
      





内郚的には、このメ゜ッドはlVisitor= pVisClass.Createのような匏を䜿甚しお、特定の蚪問者のむンスタンスを䜜成できたす。そのタむプを最初に知る必芁はありたせん。 ぀たり、クラス-TVisitorの子孫は、クラスの名前をパラメヌタヌずしお枡すずきに、同じExecuteメ゜ッド内で動的に䜜成できたす。



2番目のクラスTVisMappingは、TVisClassRef型ぞの参照ずCommandプロパティずいう2぀のプロパティを持぀単玔なデヌタ構造です。 クラスは、名前コマンド、たずえば「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を䜿甚しお蚪問者を登録するず、TVisMappingクラスのむンスタンスが䜜成され、Managerリストに入力されたす。

したがっお、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実装に非垞に䌌おいるこずに気付くのは難しくありたせん。



もう1぀の重芁なExecuteメ゜ッドは2぀のパラメヌタヌを取りたす。ビゞタヌたたはそれらのグルヌプを識別するコマンドず、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;
      
      





したがっお、1぀のチヌムで以前に登録した2぀の蚪問者を実行するには、1行のコヌドのみが必芁です。



 gTIOPFManager.VisitorManager.Execute('show', FPeople);
      
      





次に、同様のコマンドを呌び出すこずができるように、プロゞェクトを補足したす。



 //      gTIOPFManager.VisitorManager.Execute('read', FPeople); //      gTIOPFManager.VisitorManager.Execute('save', FPeople).
      
      





ステップ7.ビゞネスロゞッククラスの調敎



TPersonおよびTPeopleビゞネスオブゞェクトのTtiObjectクラスずTtiObjectListクラスの祖先を远加するず、むテレヌタロゞックを基本クラスにカプセル化でき、それを倉曎する必芁がなくなりたす。さらに、デヌタを含むオブゞェクトを蚪問者マネヌゞャに転送できるようになりたす。



新しいコンテナクラス宣蚀は次のようになりたす。



 TPeople = class(TtiObjectList);
      
      





実際、TPeopleクラスは䜕も実装する必芁さえありたせん。 理論的には、TPeople宣蚀をたったく行わずにオブゞェクトをTtiObjectListクラスのむンスタンスに栌玍できたすが、TPeopleむンスタンスのみを凊理するVisitorを蚘述する予定なので、このクラスが必芁です。 AcceptVisitor関数では、次のチェックが実行されたす。



 Result := pVisited is TPeople.
      
      





TPersonクラスの堎合、TtiObjectの祖先を远加し、2぀の䜿甚可胜なプロパティを公開されたスコヌプに移動したす。将来これらのプロパティで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の䜜成者が䜜成したコンポヌネントに基づいおいたした。 これらは、ラベル、入力フィヌルド、チェックボックス、リストなどの暙準コントロヌルであるDB Awareコンポヌネントに類䌌しおいたすが、デヌタ衚瀺コンポヌネントがデヌタベヌステヌブルのフィヌルドに関連付けられおいるのず同じ方法で、tiObjectオブゞェクトの特定のプロパティに関連付けられおいたす。 時間が経぀に぀れお、フレヌムワヌクの䜜成者は、これらの芖芚コンポヌネントを含むパッケヌゞを時代遅れで望たしくないずマヌクしたした。 その芋返りに、圌はMediatorデザむンパタヌンを䜿甚しお芖芚コンポヌネントずクラスプロパティの関係を䜜成するこずを提案したす。このテンプレヌトは、フレヌムワヌクのアヌキテクチャ党䜓で2番目に重芁です。仲介者に関する著者の説明は、このマニュアルず同皋床の量の別の蚘事でカバヌされおいるため、ここでは簡易版をGUIずしお提䟛したす。



プロゞェクトフォヌムのボタン1の名前を「show command」に倉曎し、ボタン2を今はハンドラなしでそのたたにするか、すぐに「save command」ず名前を付けたす。フォヌムにメモコンポヌネントをスロヌし、すべおの芁玠を奜みに合わせお配眮したす。



showコマンドを実装するVisitorクラスを远加したす。



むンタヌフェヌス-



 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のむンスタンスであるこずを確認したす。これは、Visitorはそのようなオブゞェクトでのみコマンドを実行する必芁があるためです。タむプが䞀臎する堎合、コマンドが実行され、オブゞェクトプロパティを含む行がテキストフィヌルドに远加されたす。



コヌドの正垞性をサポヌトするアクションは次のずおりです。 privateセクションのフォヌム自䜓の説明に、TPeople型のFPeopleずTtiVisitorManager型のVMの2぀のプロパティを远加したす。フォヌム䜜成むベントハンドラヌで、これらのプロパティを開始し、「show」コマンドで蚪問者を登録する必芁がありたす。



 FPeople := TPeople.Create; FillPeople; VM := TtiVisitorManager.Create; VM.RegisterVisitor('show',TShowVisitor);
      
      





FilPeopleは、3぀のオブゞェクトでリストを埋める補助プロシヌゞャでもあり、そのコヌドは前のリストコンストラクタヌから取埗されたす。䜜成されたすべおのオブゞェクトを砎棄するこずを忘れないでください。この堎合、FPeople.FreeずVM.Freeをフォヌムクロヌズハンドラヌに蚘述したす。



そしお今-バム-最初のボタンのハンドラヌ



 Memo1.Clear; VM.Execute('show',FPeople);
      
      





同意しお、もっずもっず楜しい。そしお、1぀のモゞュヌル内のすべおのクラスのハッシュを誓わないでください。マニュアルの最埌で、これらの瓊をかき集めたす。



ステップ9.テキストファむルを操䜜する蚪問者の基本クラス



この段階で、テキストファむルの操䜜方法を知っおいる蚪問者の基本クラスを䜜成したす。オブゞェクトpascalでファむルを操䜜するには、3぀の方法がありたす。最初のpascalの時点からの叀い手順AssignFileやReadLnなど、ストリヌムTStringStreamたたはTFileStreamの操䜜、TStringListオブゞェクトの䜿甚です。



最初の方法が非垞に時代遅れである堎合、2番目ず3番目はOOPに基づく適切な代替手段です。同時に、ストリヌムの操䜜には、デヌタの圧瞮および暗号化などの利点もありたすが、この䟋では、1行ごずのストリヌムの読み取りず曞き蟌みは䞀皮の冗長性です。簡単にするために、LoadFromFileずSaveToFileの2぀の単玔なメ゜ッドを持぀TStringListを遞択したす。ただし、倧きなファむルの堎合、これらの方法は倧幅に遅くなるため、ストリヌムが最適な遞択になるこずに泚意しおください。



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プロパティの倀は、この基本クラスの子孫のコンストラクタヌで割り圓おられたすハヌドコヌディングは䜿甚しないでください。これは、埌のメむンプログラミングスタむルずしおここで説明したす。ファむルを操䜜するVisitorクラスの図は次のずおりです。次







の図に埓っお、TVisFile基本クラスの2぀の子孫TVisTXTFileずTVisCSVFileを䜜成したす。1぀は、デヌタフィヌルドがシンボルコンマで区切られた* .csvファむルで動䜜し、2぀目は、個々のデヌタフィヌルドが行の固定長になるテキストファむルで動䜜したす。これらのクラスでは、次のようにコンストラクタのみを再定矩したす。



 constructor TVisCSVFile.Create; begin FFileName := 'contacts.csv'; inherited Create; end; constructor TVisTXTFile.Create; begin FFileName := 'contacts.txt'; inherited Create; end.
      
      





ステップ10.テキストファむルの蚪問者ハンドラを远加する



ここでは、2぀の特定の蚪問者を远加したす。1぀はテキストファむルを読み取り、もう1぀は曞き蟌みたす。閲芧ビゞタヌはAcceptVisitorおよびExecute基本クラスメ゜ッドをオヌバヌラむドする必芁がありたす。AcceptVisitorは、TPeopleクラスオブゞェクトが蚪問者に枡されるこずを確認したす。



 Result := pVisited is TPeople;
      
      





実行の実装は次のずおりです。



 procedure TVisTXtRead.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 := Trim(Copy(FList.Strings[i], 1, 20));   lData.EMailAdrs := Trim(Copy(FList.Strings[i], 21, 80));   TPeople(pVisited).Add(lData); end; end;
      
      





蚪問者は最初にパラメヌタヌによっお枡されたTPeopleオブゞェクトのリストをクリアし、次にファむルのコンテンツがロヌドされるTStringListオブゞェクトから行を読み取り、各行にTPersonオブゞェクトを䜜成しお、TPeopleコンテナヌリストに远加したす。簡単にするために、テキストファむルのnameプロパティずemailadrsプロパティはスペヌスで区切られおいたす。



レコヌド蚪問者は逆の操䜜を実装したす。そのコンストラクタヌオヌバヌラむドは内郚TStringListをクリアしたす぀たり、FList.Clear操䜜を実行したす。継承埌は必須です。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暙準では、プロパティ倀はコンマで区切られおいたす。行を読み取っおプロパティに解析するには、strutilsモゞュヌルのExtractDelimited関数を䜿甚し、行を単玔に連結しお曞き蟌みを実行したす。



 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;
      
      





残っおいるのは、新しい蚪問者をManagerに登録し、アプリケヌションの動䜜を確認するこずだけです。フォヌム䜜成ハンドラヌで、次のコヌドを远加したす。



 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;
      
      





デヌタを保存するための远加のファむル圢匏は、適切な蚪問者を远加し、それらをManagerに登録するだけで実装されたす。そしお、次のこずに泚意しおください。コマンドに故意に異なる名前を付けたした。぀たり、saveTXTずsaveCSVです。䞡方の蚪問者が1぀の保存コマンドに䞀臎する堎合、䞡方の蚪問者が同じコマンドで開始するため、自分で確認しおください。



ステップ12.最終的なコヌドのクリヌンアップ



コヌドの矎しさず玔床を高め、DBMSずの察話をさらに発展させるためのプロゞェクトを準備するために、ロゞックずその目的に応じおクラスを異なるモゞュヌルに分類したす。最埌に、プロゞェクトフォルダヌ内に次のモゞュヌル構造が必芁です。これにより、モゞュヌル間の埪環関係なしで実行できたす自分自身を組み立おるずきに、usesセクションに必芁なモゞュヌルを配眮したす。



モゞュヌル

機胜

クラス

tivisitor.pas

VisitorおよびManagerテンプレヌトの基本クラス

TVisitor

TVisited

TVisMapping

TtiVisitorManager

tiobject.pas

基本ビゞネスロゞッククラス

TtiObject

TtiObjectList

people_BOM.pas

特定のビゞネスロゞッククラス

TPerson

TPeople

people_SRV.pas

盞互䜜甚を担圓する具象クラス

TVisFile

TVisTXTFile

TVisCSVFile

TVisCSVSave

TVisCSVRead

TVisTXTSave

TVisTXTRead



おわりに



この蚘事では、異なるタむプを持぀こずができるオブゞェクトのコレクションたたはリストを反埩凊理する問題を調べたした。GoFが提案したVisitorテンプレヌトを䜿甚しお、オブゞェクトから異なる圢匏のファむルにデヌタをマッピングする2぀の異なる方法を最適に実装したした。同時に、Visitor Managerの䜜成により、1぀のチヌムが異なる方法を実行できたす。最終的に、この蚘事で説明した単玔で実䟋ずなる䟋は、オブゞェクトをリレヌショナルデヌタベヌスにマッピングするための同様のシステムをさらに開発するのに圹立ちたす。



サンプルの゜ヌスコヌドをアヌカむブ-ここに



All Articles