6

数日前に質問を投稿しましたが、その回答から、独自のクラスを作成するように言われました。

私はOOP以前の時代からの昔ながらのプログラマーで、プログラミングはよく構造化され、効率的で、組織化されていますが、Delphiとサードパーティのオブジェクトを使用する以外のカスタムOOPはありません.

Delphi 2 を使い始めたとき、Delphi のオブジェクト指向クラスがどのように機能するかを調べたことがありますが、それらは私のプログラミングのバックグラウンドにはなじみがないように思えました。コンポーネントを設計する開発者や、ユーザー インターフェースのビジュアル コントロールにとって、それらがどのように優れていたか、また優れているかを理解しています。しかし、プログラム自体のコーディングでそれらを使用する必要があるとは思いませんでした。

15 年経った今、私は Delphi のクラスと OOPing をもう一度見ています。たとえば、次のような構造を取るとします。

type
  TPeopleIncluded = record
    IndiPtr: pointer;
    Relationship: string;
  end;
var
  PeopleIncluded: TList<TPeopleIncluded>;

そうすれば、OOP 支持者はおそらくこれをクラスにするように言うでしょう。論理的には、これはジェネリック TList から継承されたクラスになると思います。これは次のように行われると思います:

TPeopleIncluded<T: class> = class(TList<T>)

しかし、それは私が立ち往生する場所であり、残りをどのように行うかについての適切な指示がありません.

Delphi が Generics.Collections ユニットの例として持っているクラスを見ると、次のように表示されます。

TObjectList<T: class> = class(TList<T>)
private
  FOwnsObjects: Boolean;
protected
  procedure Notify(const Value: T; Action: TCollectionNotification); override;
public
  constructor Create(AOwnsObjects: Boolean = True); overload;
  constructor Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean = True); overload;
  constructor Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean = True); overload;
  property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;
end;

コンストラクタとプロシージャの定義は次のとおりです。

{ TObjectList<T> }

constructor TObjectList<T>.Create(AOwnsObjects: Boolean);
begin
  inherited;
  FOwnsObjects := AOwnsObjects;
end;

constructor TObjectList<T>.Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean);
begin
  inherited Create(AComparer);
  FOwnsObjects := AOwnsObjects;
end;

constructor TObjectList<T>.Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean);
begin
  inherited Create(Collection);
  FOwnsObjects := AOwnsObjects;
end;

procedure TObjectList<T>.Notify(const Value: T; Action: TCollectionNotification);
begin
  inherited;
  if OwnsObjects and (Action = cnRemoved) then
    Value.Free;
end;

この「単純な」クラス定義は、Delphi で OOP を何年も使用してきた人には明らかかもしれませんが、私にとっては、何を使用し、どのように使用するかについて、何百もの未回答の質問を提供するだけです。 .

私には、これは科学ではないようです。これは、情報をオブジェクトに最適に構造化する方法の芸術のようです。

したがって、この質問は、これについて本当に助けが必要なので、閉じられないことを願っています.Delphiを使用してクラスを作成するための最良の指示をどこで、またはどのように取得するか、そして適切なDelphiの方法でそれを行う方法.

4

1 に答える 1

10

私には、これは科学ではないようです。これは、情報をオブジェクトに最適に構造化する方法の芸術のようです。

まあ、そうだろう。正式な要件はあまりありません。これは、アイデアを整理し、途中で多くの重複を排除するのに役立つ一連のツールにすぎません。

そうすれば、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ので、あなたの会社がスカイダイビングの結婚式を行っているふりをするつもりです.同じ飛行機から飛び降りる予定です...そして、幸せなカップルに正しい色のパラシュートを与えることができるように、彼らを知ることが非常に重要です.IndiJumpIDRelationship

明らかに、それはあなたのドメインと正確に一致するわけではありませんが、ここで一般的な質問をしているので、詳細はあまり重要ではありません.

他の投稿の人々があなたに伝えようとしていたことは (私の推測ですが)、リストをクラスに置き換えることではなく、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継承しますが、 も追加します。カスタム実装での動作をオーバーライドします。( のマーカーに対応するここのマーカーのおかげで、これを行っていることがわかります。InitGetManifestTJumpGetWeddingManifest( );PrintManifest()overridevirtualTJump

しかしここで、これ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 >;TWeddingJumpTList< 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ます。PrintManifestTJumpTWeddingJump

まだ 1 つの問題があります。宣言はしPrintManifestRow(passenger:TPassenger)ましたが、実際には . を渡していTWeddingGuestます。TWeddingGuestは ...のサブクラスであるため、これは正当TPassengerです。しかし、フィールドに到達する必要がありますが、その.relationshipフィールドはありTPassengerません。

コンパイラーは、通常の ではなくTWeddingJump常に a を渡すことを a 内でどのように信頼できますか? フィールドが実際にそこにあることを保証する必要があります。TWeddingGuestTPassengerrelationship

TWeddingJupmp.(passenger:TWeddingGuest)サブクラス化することにより、基本的に親クラスが実行できるすべてのことを実行することを約束し、親クラスは任意 の を処理できるため、単に宣言することはできませんTPassenger

したがって、型指定されていないポインターと同じように、手動で型をチェックしてキャストすることに戻ることができますが、これを処理するより良い方法があります。

  • ポリモーフィズム アプローチ:PrintManifestRow()メソッドをクラスに移動しTPassenger(パラメーターを削除しpassenger:TPassengerます。これは暗黙的なパラメーターになったためSelf)、そのメソッドを でオーバーライドしてから、 callを使用TWeddingGuestするだけです。TJump.PrintManifestpassenger.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)

とにかく、それは私がこれらすべてについて書くことを期待していたよりもずっと多くのことです. お役に立てば幸いです。:)

于 2012-07-01T21:39:18.970 に答える