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つのチームが異なる方法を実行できます。最終的に、この記事で説明した単純で実例となる例は、オブジェクトをリレーショナルデータベースにマッピングするための同様のシステムをさらに開発するのに役立ちます。

サンプルのソースコードをアーカイブ-ここに

Source: https://habr.com/ru/post/J441266/


All Articles