4

基本クラスにインターフェイスを実装すると、そのサブクラスに継承されます。関数/手順が継承されることはわかっていますが、サブクラスをインターフェイスにキャストしてからそのインターフェイスに戻すことができるかどうかにもっと興味がありますオリジナルクラス。

私ができることを望んでいるのは、さまざまな基本クラスのオブジェクトを関数に渡し、関数で型を決定し、必要に応じて使用することです。

これは可能ですか、それは正しいアプローチですか?

アップデート

混乱を解消する(またはさらに作成する)ために、ここで私がやりたいことを示します(その核心にストライプ化します)。

インターフェース

    IMyInterFace = interface
   ['{B7203E50-F5CB-4755-9DB1-FB41B7B192C5}'] 
     function MyFunction: Boolean;
   end;

基本クラス

   type TMyObject = class(TInterfacedObject,IMyInterFace)

サブクラス

  type TSubMyObject = class(TMyObject)

別のクラス

  type TMyOtherObject = class(TInterfacedObject,IMyInterFace)

それでは使い方

 procedure foo();
   var obj:TSubMyObject;
 begin
      obj := TSubMyObject.Create();
      SendInterfaceObject(obj);
 end;

 procedure bar();
   var obj:TMyOtherObject;
 begin
      obj := TMyOtherObject.Create();
      SendInterfaceObject(obj);
 end;



 procedure SendInterfaceObject(iobj:IMyInterFace);
 begin
     if (iobj is TSubMyObject) then
     begin
        //code here
     end
     else if (iobj is TMyOtherObject) then
     begin
       //code here
     end;
 end;

更新 2

私はコードを少し更新したので、私がより良いものを示しています。

//code hereのセクションは、渡されるオブジェクトとはほとんど関係がありません。たとえば、このクラスが TAccounts で、TEmployee オブジェクトが渡された場合、毎週の支払いが行われる可能性がありますが、TInvoice の場合はチェックされます。支払いが必要かどうかを確認し、日付が締め切りの 2 日前になったときにのみ支払います。

TEmployee/TInvoice は、支払いを要求する外部クラスから来ることさえあります。

これはほんの一例です。

4

5 に答える 5

9

はい、インターフェイスはサブクラスによって継承されます。

サブクラスからインターフェイスにキャストすることはまったく問題ありません。

ただし、質問を間違って読んでいる場合はお詫びしますが、「その後、元のクラスに戻る」とは. . .

インターフェイス I、クラス A、およびクラス B があります。A は I を実装し、B は A を継承します。おそらく可能ですが、A から B にキャストしないでください。

編集:

B から I に行き、 B に戻りたいとします。. . ただし、B が関数に渡すものである場合は、既に B への参照があるため、I から B にキャストする必要はありません (別のオブジェクトについて話している場合を除き、いいえ、実行しないでください)。

I から B に移動することは、A から B に移動するのと同じです。継承チェーンをキャストしようとしていますが、これは実際に行うべきではありません。これを行う必要があるのはコードのにおいです。この問題を別の方法で解決する必要があることを示しています (おそらく、クラスを再設計する (たとえば、I にプロパティ/メソッドを追加する) か、関数が機能するだけであると判断する)サブクラスを使用 - サブクラス 'B' を使用すると、A & I のすべてのメソッドにアクセスできます)。

質問を編集して、やろうとしていることのサンプル コードを追加できますか?

編集2

 procedure SendInterfaceObject(iobj:IMyInterFace);
 begin
     if (iobj is TSubMyObject) then
     begin
        //code here
     end;
 end;

そこにある「If」ステートメントは悪い考えであり、OO プリンシパルを壊します。これを行う必要がある場合は、

  • インターフェイス定義が不十分です。(iObj.SomeProperty = 1 の場合) を可能にするインターフェイスに Type プロパティを追加することをお勧めします。. .)
  • インターフェイスはこの問題に対する正しい解決策ではありません。参照を TSubMyObject として渡す必要があります。

編集3:

@mghie:私はあなたに同意します。私がうまく説明できなかったのは、関数がそこに分岐できるようにするデータが SomeProperty にあり、型チェックの依存関係を削除することでした。SomeProperty は型チェックを '単純に' 置き換えるべきではありません (たとえば、クラス名をプロパティに入れ、次にクラス名をチェックすることによって)。それはまったく同じ問題です。

インターフェイスを継承するサブクラスの間には、いくつかの本質的な違いがあります。この違いは、次のいずれかで表す必要があります。

  • ブランチで使用できるデータ項目を公開する

例えば

if(item.Color = Red) then 
   item.ContrastColor := Blue;
else
   item.ContrastColor := Red;
  • またはポリモーフィズムなどを通じて

IEmployee は CalculatePay メソッドを定義し、TManager と TWorker は IEmployee を実装します。それぞれ CalculatePay メソッドのロジックが異なります。

最初のケースのようなことをすることが意図されている場合、ポリモーフィズムやり過ぎになる可能性があります (ポリモーフィズムはすべての問題を解決するわけではありません)。

編集4

「//code here のセクションは、渡されるオブジェクトとはほとんど関係がない. . .」とあなたは言います。申し訳ありませんが、その説明は正しくありません。従業員に支払う必要がある場合は、従業員の 1) EmployeeCode 2) 給与の詳細 3) 銀行の詳細など、必要な請求書を請求する場合は 1) InvoiceNumber を知る必要があります。 2) 請求金額 3) 請求する CustomerCode など . . . これはポリモーフィズムにとって理想的な場所です

「アカウント」がオブジェクトで何かをする必要があるかどうかを確認するためにインターフェースをチェックする関数を考えてみましょう (例えば、従業員への支払い、請求書の請求など)。したがって、関数 AccountsCheck を呼び出すことができます。Inside Accounts check には、各サブクラスに固有のロジックの断片があります (従業員への支払い、請求書への請求など)。これは、ポリモーフィズムの理想的な候補です。

あなたのインターフェースで(または別のインターフェースで、またはサブクラスの仮想メソッドとして)「AccountsCheck」メソッドを定義します。次に、各派生クラスは Accounts チェックの独自の実装を取得します。

コードは、巨大な単一の AccountsCheck 関数から、各サブクラスの小さな関数に移動します。これにより、コードが作成されます

  • より明白な意図 (各クラスには AccountsCheck のロジックが含まれています)
  • AccountsCheck for C で何かを修正するときに、サブクラス B のロジックを壊す可能性が低くなります。
  • サブクラス B の AccountsCheck ロジックが何であるかを正確に把握する方が簡単です。一般的な AccountsCheck では 200 行ではなく、小さな AccountsCheck では 20 行のコードをチェックするだけです)。

これには他にも「正当な理由」があります。誰かがコメントを編集/投稿したい場合は、そうしてください。

AccountsCheck の実装間でロジックを共有する必要がある場合は、いくつかのユーティリティ関数を作成し、それぞれで同じホイールを再実装しないでください。

ポリモーフィズムはあなたの問題の解決策です。

于 2009-02-05T10:32:22.360 に答える
2

ここでの私の提案は、クラスに対してキャストするのではなく、別のインターフェイスに対してキャストすることです。TMyOtherObject を次のように変更します。

type
  IOtherObjectIntf = interface
    ['{FD86EE29-ABCA-4D50-B32A-24A7E71486A7}']
  end;

type 
  TMyOtherObject = class(TInterfacedObject,IMyInterFace,IOtherObjectIntf)

次に、他のルーチンを次のように変更します。

procedure SendInterfaceObject(iobj:IMyInterFace);
begin
  if Supports(iObj,IOtherObjectIntf) then
    begin
      //code here for TMyOtherObject
    end
  else 
    begin
      //code here for other standard implementations
    end;
end;

このようにして、TMyOtherObject の「カスタム」コードは、追加のカスタム コードなしで ITS の子孫にも適用されます。IOtherObjectIntf インターフェイスは、コードが適切に分岐できるようにする「はい、私はそれらの 1 人です」というインジケータとしてのみ使用されます。確かに、それは別のガイドを荒らしている...しかし、それらの多くは、誰が気付くでしょうか?:)

于 2009-02-05T21:33:05.943 に答える
1

あなたの質問がどのように理解されるべきかについては疑問があるようです。実際、この回答に対するあなたのコメントでは、「BからI、Bに行きたい」と言っています。

これは実際には推奨されておらず、クラスでのインターフェースの実装方法に関する情報を使用することによってのみサポートされます。

私があなたを正しく理解しているなら、あなたがしたいことは、あるメソッドにインターフェースを渡すことです。そのメソッドでは、インターフェースが実装された具体的なクラスに応じて異なることを行います。ただし、インターフェイスを使い始めたら、引き続きインターフェイスを使用することをお勧めします。インターフェイスに実装クラスを返すメソッドを持たせることもできますが、インターフェイスが実装されているクラスについては何も想定しないでください。インターフェイスに対するプログラミングの利点の一部が犠牲になります。

代わりにできることは、さまざまなインターフェースを作成し、それらのいくつかを子孫クラス(のいくつか)にのみ実装することです。次に、渡されたインターフェイスポインタでQueryInterface()またはSupports()を使用できます。基本クラスの場合、これはnilを返しますが、インターフェイスを実装するすべての子孫クラスの場合、それらが持つメソッドのみを呼び出すことができるポインターを返します。

編集:たとえば、OmniThreadLibraryには次のものがあります。

IOmniWorker = interface
  ['{CA63E8C2-9B0E-4BFA-A527-31B2FCD8F413}']
  function  GetImplementor: TObject;
  ...
end;

これをインターフェースに追加できます。しかし、繰り返しになりますが、IMHOでは個別のインターフェイスを使用する方がはるかに優れています。

于 2009-02-05T12:56:19.527 に答える
1

インターフェイスをオブジェクトに直接キャストすることはできません (インターフェイスが意図されているものではありません)。

本当にそのようにしたい場合は、mghie によって提供された例「IOmniWorker」を IMyInterFace で直接使用できます。

IMyInterFace = interface
['{B7203E50-F5CB-4755-9DB1-FB41B7B192C5}'] 
  function MyFunction: Boolean;
  function GetImplementor: TObject;
end;

関数の実装は次のようになります。

function TMyObject.GetImplementor: TObject;
begin
  Result := Self;
end;
function TMyOtherObject.GetImplementor: TObject;
begin
  Result := Self;
end; 

SendInterfaceObject は次のようになります。

procedure SendInterfaceObject(const iobj:IMyInterFace);
begin
  if (iobj.GetImplementor is TSubMyObject) then
  begin
     //code here
  end
  else if (iobj.GetImplementor is TMyOtherObject) then
  begin
    //code here
  end;
end;

ところで、私は (非常に) 小さな最適化を追加しました: iobj を "const" として関数に渡すことで、不要な参照カウントを避けることができます。

于 2009-02-06T13:39:19.127 に答える
1

インターフェイスはサブクラスによって継承され、オブジェクトをインターフェイスにキャストできますが、インターフェイスをクラスにキャストすることは安全ではありません (または推奨されません)。これを行う必要がある場合は、おそらくインターフェイスを間違った方法で使用しています。

于 2009-02-05T12:19:34.343 に答える