web-dev-qa-db-ja.com

デフォルトの実装または抽象メソッド?

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

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

_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)。

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

24
Scott

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

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

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

15
Jon Skeet

イベントが発生したときにそのブール変数を設定することを心配しているようです。ユーザーが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);
  }
});
1
Michael

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

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

1
Steve

仕事で他の人と一緒に開発していたアプリケーションの基本的なアウトライン(クラス階層)を作成したときに、同様のシナリオに遭遇しました。メソッドを配置するための私の選択abstract(したがって、その実装を強制するため)はcommunicationの目的でした。

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

基本クラスのデフォルトの実装は見過ごされがちです。

1
Juri

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

スコット

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

0
Woot4Moo