31

メソッドのデフォルトの実装をスーパークラスに配置し、サブクラスがこれから逸脱したい場合にそれをオーバーライドする方がよいでしょうか?それとも、スーパークラスのメソッドを抽象化したままにして、多くのサブクラスで通常の実装を繰り返すべきでしょうか?

たとえば、私が関与しているプロジェクトには、停止する条件を指定するために使用されるクラスがあります。抽象クラスは次のとおりです。

public abstract class HaltingCondition{
    public abstract boolean isFinished(State s);
}

些細な実装は次のようになります。

public class AlwaysHaltingCondition extends HaltingCondition{
    public boolean isFinished(State s){
        return true;
    }
}

オブジェクトでこれを行う理由は、これらのオブジェクトを任意に一緒に構成できるからです。例えば:

public class ConjunctionHaltingCondition extends HaltingCondition{
    private Set<HaltingCondition> conditions;

    public void isFinished(State s){
        boolean finished = true;
        Iterator<HaltingCondition> it = conditions.iterator();
        while(it.hasNext()){
            finished = finished && it.next().isFinished(s);
        }
        return finished;
    }
}

ただし、イベントが発生したことを通知する必要がある停止条件がいくつかあります。例えば:

public class HaltAfterAnyEventHaltingCondition extends HaltingCondition{
    private boolean eventHasOccurred = false;

    public void eventHasOccurred(Event e){
        eventHasOccurred = true;
    }

    public boolean isFinished(State s){
        return eventHasOccurred;
    }
}

eventHasOccurred(Event e)抽象スーパークラスでどのように表現するのが最適でしょうか? ほとんどのサブクラスは、このメソッドの no-op 実装を持つことができます (例: AlwaysHaltingCondition)。一部のサブクラスは、正しく動作するために重要な実装を必要とし (例HaltAfterAnyEventHaltingCondition:それらは正しく動作します (例: ConjunctionHaltingCondition)。

コードの重複を減らすデフォルトの実装を持つことができますが、一部のサブクラスがコンパイルされても、オーバーライドされていない場合は正しく動作しません。または、メソッドを抽象として宣言することもできます。彼らが提供していた実装について考えてみてください。これらの戦略のその他の長所と短所は何ですか? 一方は他方よりもはるかに優れていますか?

4

5 に答える 5

20

1 つのオプションは、別の抽象サブクラスを持ち、デフォルトの実装を使用たいすべての実装のスーパークラスとして使用することです。

個人的には、通常、非最終メソッドを抽象クラスに残します (または代わりにインターフェイスを使用します) が、それは状況によって異なります。たとえば、多くのメソッドを持つインターフェイスがあり、そのうちのいくつかをオプトインできるようにしたい場合は、すべてのメソッドに対して何もしない方法でインターフェイスを実装する抽象クラスで問題ありません。

基本的に、各ケースのメリットを評価する必要があります。

于 2010-08-29T17:16:46.643 に答える
1

イベントが発生したときにそのブール変数を設定することを心配しているようです。ユーザーが をオーバーライドeventHasOccurred()すると、ブール変数が設定されisFinished()ず、正しい値が返されません。これを行うには、ユーザーがオーバーライドしてイベントを処理する 1 つの抽象メソッドと、その抽象メソッドを呼び出してブール値を設定する別のメソッドを使用できます (以下のコード サンプルを参照)。

また、eventHasOccurred()メソッドをHaltingConditionクラスに入れる代わりに、イベントを処理する必要があるクラスに、このメソッドを定義するクラスを拡張させることができます (以下のクラスのように)。イベントを処理する必要のないクラスは、次のように拡張できますHaltingCondition

public abstract class EventHaltingCondition extends HaltingCondition{
  private boolean eventHasOccurred = false;

  //child class implements this
  //notice how it has protected access to ensure that the public eventHasOccurred() method is called
  protected abstract void handleEvent(Event e);

  //program calls this when the event happens
  public final void eventHasOccurred(Event e){
    eventHasOccurred = true; //boolean is set so that isFinished() returns the proper value
    handleEvent(e); //child class' custom code is executed
  }

  @Override
  public boolean isFinished(){
    return eventHasOcccurred;
  }
}

編集(コメントを参照):

final EventHaltingCondition condition = new EventHaltingCondition(){
  @Override
  protected void handleEvent(Event e){
    //...
  }
};
JButton button = new JButton("click me");
button.addActionListener(new ActionListener(){
  public void actionPerformed(ActionEvent actionEvent){
    //runs when the button is clicked

    Event event = //...
    condition.eventHasOccurred(event);
  }
});
于 2010-08-29T17:35:52.387 に答える
1

抽象基本クラスに実装を配置する場合は、no-op 実装を使用するサブクラスのコードにする必要があります。これは、基本クラスにも意味のある実装であるためです。基本クラスに適切な実装がなかった場合 (たとえば、ここで説明しているメソッドに適切な no-op がなかった場合)、抽象化したままにしておくことをお勧めします。

複製されたコードに関して、メソッドの同じ実装をすべて使用するクラスの「ファミリ」があり、ファミリ内のすべてのクラスでコードを複製したくない場合は、ファミリごとにヘルパー クラスを使用するだけで済みます。これらの実装。あなたの例では、イベントを渡すクラスのヘルパー、イベントを受け入れて記録するクラスのヘルパーなどです。

于 2010-08-29T17:39:24.870 に答える
1

仕事で共同開発していたアプリケーションの基本的なアウトライン (クラス階層) を作成したときに、同様のシナリオに遭遇しました。メソッドの抽象化を配置する(そしてその結果としてその実装を強制する)という私の選択は、コミュニケーションを目的としたものでした。

基本的に、他のチームメイトは何らかの方法でメソッドを明示的に実装する必要があったため、まずその存在に気づき、次に、それがデフォルトの実装であっても、そこで返されるものに同意します。

基本クラスのデフォルトの実装は、見落とされることがよくあります。

于 2010-08-30T19:09:22.673 に答える
0

スーパー クラス メソッドの抽象化を終了する場合は、インターフェイスの利用を検討することをお勧めします (インターフェイスと混同しないでください)。インターフェイスは具体的な実装を提供しないものであるため。

スコット

拡張すると、私たちプログラマーが、多くの場合新しく、時には経験豊富なインターフェイスにコーディングするように指示された場合、開発者は、実装の詳細が見つからない可能性があるキーワード Interface を参照していると誤って想定します。ただし、これをより明確に言うと、トップレベルのオブジェクトは、対話可能なインターフェイスとして扱うことができます。たとえば、Animal と呼ばれる抽象クラスは、Animal から継承される Cat と呼ばれるクラスのインターフェイスになります。

于 2010-08-29T17:28:04.520 に答える