私には、これは科学ではないようです。これは、情報をオブジェクトに最適に構造化する方法の芸術のようです。
まあ、そうだろう。正式な要件はあまりありません。これは、アイデアを整理し、途中で多くの重複を排除するのに役立つ一連のツールにすぎません。
そうすれば、OOP 支持者はおそらくこれをクラスにするように言うでしょう。論理的には、これはジェネリック TList から継承されたクラスになると思います。
実際、汎用コンテナーの要点は、オブジェクトの種類ごとに新しいコンテナー クラスを作成する必要がないことです。代わりに、新しいコンテンツ クラスを作成してから、TList<TWhatever>
.
クラス インスタンスは、レコードへのポインタと考えてください。
さて: レコードへのポインターを使用できるのに、なぜクラスを使用するのでしょうか? いくつかの理由:
- encapsulation : キーワードを使用して実装の一部の側面を非表示にすると、
private
他の開発者 (将来の自分を含む) が、変更される可能性のある実装の詳細や概念を理解する上で重要ではない実装の詳細に依存しないことを知ることができます。
- ポリモーフィズム: 各レコードに関数へのポインターのセットを与えることで、多くの特別なディスパッチ ロジックを回避できます。次に、オブジェクトのタイプごとに異なることを行う大きなステートメントを用意するのではなく
case
、リストをループして各オブジェクトに同じメッセージを送信し、関数ポインターに従って何をすべきかを決定します。
- 継承: 関数とプロシージャへのポインターを使用してレコードを作成し始めると、プロシージャの 1 つまたは 2 つを変更する必要があることを除いて、既に持っているものと非常によく似た新しい関数ディスパッチ レコードが必要になる場合がよくあることがわかります。 . サブクラス化は、それを実現するための便利な方法です。
したがって、他の投稿で、プログラム全体が次のようになることを示しました。
procedure PrintIndiEntry(JumpID: string);
var PeopleIncluded : TList<...>;
begin
PeopleIncluded := result_of_some_loop;
DoSomeProcess(PeopleIncluded);
end;
私には何を意味するのIndi
かはっきりしないJumpID
ので、あなたの会社がスカイダイビングの結婚式を行っているふりをするつもりです.同じ飛行機から飛び降りる予定です...そして、幸せなカップルに正しい色のパラシュートを与えることができるように、彼らを知ることが非常に重要です.Indi
JumpID
Relationship
明らかに、それはあなたのドメインと正確に一致するわけではありませんが、ここで一般的な質問をしているので、詳細はあまり重要ではありません.
他の投稿の人々があなたに伝えようとしていたことは (私の推測ですが)、リストをクラスに置き換えることではなく、JumpID をクラスに置き換えることでした。
つまり、プロシージャに渡しJumpID
てそれを使用してデータベースから人のリストを取得するのではなく、Jump
クラスを作成します。
そして、JumpID が実際に のようにジャンプを示している場合、goto
おそらく実際には、すべてが同じものをサブクラス化し、同じメソッドをさまざまな方法でオーバーライドする一連のクラスになるでしょう。
実際、結婚式ではないパーティーを開くと仮定しましょう。その場合、リレーションシップは必要なく、単純な人のリストだけが必要です。
type TPassenger = record
FirstName, LastName: string;
end;
type TJump = class
private
JumpID : string;
manifest : TList< TPassenger >;
public
constructor Init( JumpID: string );
function GetManifest( ) : TList< TPassenger >;
procedure PrintManifest( ); virtual;
end;
これPrintManifest()
で のジョブが実行されますPrintIndyEntry()
が、インラインでリストを計算する代わりに、 が呼び出されますSelf.GetManifest()
。
データベースはそれほど変更されず、TJump
インスタンスの寿命は常に短いのでSelf.manifest
、コンストラクターにデータを入力するだけにすることにします。その場合、GetManifest()
そのリストを返すだけです。
または、データベースが頻繁に変更されるかTJump
、データベースがその下で変更される可能性があるほど長く固執している可能性があります。その場合、GetManifest()
呼び出されるたびにリストを再構築します... またはprivate
、最後に照会した時間を示す別の値を追加し、情報の有効期限が切れた後にのみ更新します。
ポイントは、その情報を隠しているため、PrintManifest
どのように機能するかを気にする必要がないということです。GetManifest
もちろん、Delphi では、unit
キャッシュされたパッセンジャー リストのリストをimplementation
セクションに隠して、同じことを で行うこともできました。
しかし、ウェディング パーティー固有の機能を実装するときが来ると、classess はもう少しテーブルにもたらします。
type TWeddingGuest = record
public
passenger : TPassenger;
Relationship : string;
end;
type TWeddingJump = class ( TJump )
private
procedure GetWeddingManifest( ) : TList< TWeddingGuest >;
procedure PrintManifest( ); override;
end;
ここでは、 はからとをTWeddingJump
継承しますが、 も追加します。カスタム実装での動作をオーバーライドします。( のマーカーに対応するここのマーカーのおかげで、これを行っていることがわかります。Init
GetManifest
TJump
GetWeddingManifest( );
PrintManifest()
override
virtual
TJump
しかしここで、これPrintManifest
が実際にはかなり複雑な手順であり、ヘッダーに列を 1 つ追加し、関係フィールドをリストする本文に別の列を追加するだけの場合に、そのコードをすべて複製したくないとします。次のようにできます。
type TJump = class
// ... same as earlier, but add:
procedure PrintManfestHeader(); virtual;
procedure PrintManfiestRow(passenger:TPassenger); virtual;
end;
type TWeddingJump = class (TJump)
// ... same as earlier, but:
// * remove the PrintManifest override
// * add:
procedure PrintManfestHeader(); override;
procedure PrintManfiestRow(passenger:TPassenger); override;
end;
今、あなたはこれをしたい:
procedure TJump.PrintManifest( )
var passenger: TPassenger;
begin;
// ...
Self.PrintManifestHeader();
for guest in Self.GetManifest() do begin
Self.PrintManifestRow();
end;
// ...
end;
しかし、GetManifest()
あなたはまだできません。TList< TPassenger >;
TWeddingJump
TList< TWeddingGuest >
さて、どうやってそれを処理できますか?
元のコードでは、次のようになります。
IndiPtr: pointer
何へのポインタ?私の推測では、この例のように、さまざまな種類の個人があり、さまざまなことを行う必要があるため、汎用ポインターを使用して、さまざまな種類のレコードを指すようにし、それを次のようにキャストすることを願っています。正しいことは後で。しかし、クラスには、この問題を解決するためのより良い方法がいくつかあります。
TPassenger
クラスを作成してGetRelationship()
メソッドを追加できます。これにより、 の必要がなくなりますが、結婚式について話していないときでも、メソッドが常にTWeddingGuest
存在することを意味します。GetRelationship
GetRelationship(guest:TPassenger)
クラスに aを追加してTWeddingGuest
、それを内部で呼び出すことができTWeddingGuest.PrintManifestRow()
ます。
しかし、その情報を入力するためにデータベースにクエリを実行する必要があるとします。上記の 2 つの方法では、乗客ごとに新しいクエリを発行しているため、データベースが停止する可能性があります。で、1 回のパスですべてを取得する必要がありGetManifest()
ます。
そのため、代わりに継承を再度適用します。
type TPassenger = class
public
firstname, lastname: string;
end;
type TWeddingGuest = class (TPassenger)
public
relationship: string;
end;
は乗客のリストを返し、すべての結婚式のゲストは乗客であるためGetManifest()
、次のようにすることができます。
type TWeddingJump = class (TJump)
// ... same as before, but:
// replace: procedure GetWeddingManfiest...
// with:
procedure GetManifest( ) : TList<TPassenger>; override;
// (remember to add the corresponding 'virtual' in TJump)
end;
ここで、 の詳細を入力すると、 と の両方で同じバージョンの が機能しTWeddingJump.PrintManifestRow
ます。PrintManifest
TJump
TWeddingJump
まだ 1 つの問題があります。宣言はしPrintManifestRow(passenger:TPassenger)
ましたが、実際には . を渡していTWeddingGuest
ます。TWeddingGuest
は ...のサブクラスであるため、これは正当TPassenger
です。しかし、フィールドに到達する必要がありますが、その.relationship
フィールドはありTPassenger
ません。
コンパイラーは、通常の ではなくTWeddingJump
常に a を渡すことを a 内でどのように信頼できますか? フィールドが実際にそこにあることを保証する必要があります。TWeddingGuest
TPassenger
relationship
TWeddingJupmp.(passenger:TWeddingGuest)
サブクラス化することにより、基本的に親クラスが実行できるすべてのことを実行することを約束し、親クラスは任意 の を処理できるため、単に宣言することはできませんTPassenger
。
したがって、型指定されていないポインターと同じように、手動で型をチェックしてキャストすることに戻ることができますが、これを処理するより良い方法があります。
- ポリモーフィズム アプローチ:
PrintManifestRow()
メソッドをクラスに移動しTPassenger
(パラメーターを削除しpassenger:TPassenger
ます。これは暗黙的なパラメーターになったためSelf
)、そのメソッドを でオーバーライドしてから、 callを使用TWeddingGuest
するだけです。TJump.PrintManifest
passenger.PrintManifestRow()
- ジェネリック クラス アプローチ:それ自体を
TJump
ジェネリック クラス (type TJump<T:TPassenger> = class
) にし、GetManifest()
return a の代わりに returnTList<TPassenger>
にしますTList<T>
。同様に; にPrintManifestRow(passenger:TPassenger)
なりPrintManifestRow(passenger:T)
ます。これTWeddingJump = class(TJump<TWeddingGuest>)
で、オーバーライドされたバージョンを自由に宣言できるようになりましたPrintManifestRow(passenger:TWeddingGuest)
。
とにかく、それは私がこれらすべてについて書くことを期待していたよりもずっと多くのことです. お役に立てば幸いです。:)