8

私は数ヶ月前にたくさんのコードを書いていましたが、今はそれに何かを追加しています。関数の約2/3が抽象で、残りの1/3が仮想であるクラスから派生した一連の関数を作成したことに気付きました。

私は見るのにかなりうんざりしています:

function descendent.doSomething() : TList;
begin
   inherited;
end;

基本クラス用にこれを持っているとき:

function descendent.doSomething() : TList;
begin
   result := nil;
end;

そして、次のようになってしまうのは嫌いだろう。

function descendent.doSomething() : TList;
begin

end;

そして、なぜ何かがうまくいかなかったのか疑問に思います。

一部の関数を実装しなかったために抽象エラーが発生する可能性があるかどうかをコンパイラーが通知するため、抽象関数を使用するのが好きです。

私の質問は、私はまだ比較的新しいDelphiプログラマーであり、8年間何も維持する必要がなかったので、この方法でコードを整理する(つまり、継承したばかりの関数を削除する)のに時間をかける価値があるかどうかです。基本クラスの関数を抽象から具体に変更します)

4

3 に答える 3

7

それはいつものように問題に依存します。インターフェイスを使用して、一連のクラスのユーザー インターフェイスを定義します。少なくとも、基礎となる実際のクラスの実装が複数あることがわかっている場合。たとえば、次のようなものがあります。

 IAllInterfaced = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassA = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassB = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

ここでは、共通の祖先はありません。各クラスはインターフェイスを実装するだけで、共通の基本クラスの形式で共通の基礎となる構造を持ちません。これは、実装が非常に異なっていて何も共有していない場合に可能ですが、彼はそれ自体をインターフェースします。派生クラスのユーザーに対して一貫性を保つために、同じインターフェイスを使用する必要があります。

2 番目のオプションは次のとおりです。

 IAllAbstract = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllAbstract_Custom = (TInterfacedObject, IAllAbstract)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual; abstract;
  end;

  TAllAbstract_ClassA = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllAbstract_ClassB = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

ここには、すべてのクラスの基本クラスがあります。そのクラスでは、共通のプロパティや他のクラスのイベントなどを使用できます...ただし、すべてのプロシージャは共通のタスクを実行しないため、抽象としてマークされます。抽象化により、それらが派生クラスに実装されることが保証されますが、すべてのクラスに「FieldA」を実装する必要はなく、「TAAllAbstract_Custom」にのみ実装します。これにより、DRY 原則が確実に使用されます。

最後のオプションは次のとおりです。

 IAllVirtual = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllVirtual_Custom = (TInterfacedObject, IAllVirtual)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual;
  end;

  TAllVirtual_ClassA = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllVirtual_ClassB = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

ここでは、すべての派生クラスに共通の基本仮想プロシージャがあります。これにより、派生クラスのレベルですべての手順を実装する必要がなくなります。コードの一部のみをオーバーライドするか、まったくオーバーライドできません。

当然のことながら、これはすべて特殊なケースであり、それらを回避する余地があります。これらの概念を混在させることができます。

覚えとけ:

  1. インターフェイスは、実装をユーザーから隠し、共通の使用ポイント (インターフェイス) を確保するための強力なツールです。また、インターフェイスを完全に実装する必要があるため、いくつかの規範の使用を強制します。
  2. 抽象は優れたツールであるため、実際には必要のない手順に空のスタブを使用する必要はありません。反対に、それらは派生クラスでそれらを実装することを強制します。
  3. Virtual は、すべてのクラスで実装する必要があり、明確な OP および DRY 原則を保証する共通コードがある場合に便利です。すべての派生クラスが持っている、または必要としないプロシージャがある場合にも歓迎されます。

長い回答で申し訳ありませんが、ここには簡単な説明がありません。それはすべて当面の問題に依存します。派生クラスの共通点と実装の違いのバランスです。

于 2010-09-27T17:03:11.760 に答える
1

コードが本当に単純で、読みにくくエラーが発生しやすい場合は、おそらく読みにくくエラーが発生しやすいでしょう。(一方、コードが複雑で読みにくい場合は、経験不足である可能性があります。ただし、このようなものではありません。)問題がまだ残っている間に、今すぐリファクタリングすることをお勧めします。あなたの心の中で新鮮です。

于 2010-09-27T16:13:33.707 に答える
1

はい、コードを整理してください。

これにより、他のコードがはるかに読みやすくなります (既に述べたように、実際に上書きされたメソッドを確認しやすくなります)。追加の利点として、親クラスのメソッドのシグネチャを簡単に変更できるようになります。仮想メソッドにもう 1 つのパラメーターを渡すことにしたとします。親クラスに変更を加えると、指定された親クラスから継承するすべての子クラスに対して同じ変更を繰り返す必要があります。その時点で、「継承」を呼び出すだけの偽の上書きメソッドは必要ありません。

于 2010-09-28T10:21:24.737 に答える