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)我们应该如何在抽象超类中最好地表示?大多数子类可以有这个方法的无操作实现(例如AlwaysHaltingCondition),而有些需要重要的实现才能正确操作(例如HaltAfterAnyEventHaltingCondition),而其他子类不需要对消息做任何事情,但必须将其传递给它们的下属,以便它们将正常运行(例如ConjunctionHaltingCondition)。

我们可以有一个默认实现,这将减少代码重复,但如果没有覆盖它会导致一些子类编译但不能正确运行,或者我们可以将方法声明为抽象,这将要求每个子类的作者想想他们提供的实现,尽管十分之九是无操作实现。这些策略的其他优点和缺点是什么?一个比另一个好吗?

4

5 回答 5

20

一种选择是拥有另一个抽象子类,用作所有想要使用默认实现的实现的超类。

就我个人而言,我通常将非最终方法抽象留在抽象类中(或者只使用接口),但这绝对取决于具体情况。例如,如果您有一个包含许多方法的接口,并且您希望能够只选择其中一些方法,那么一个抽象类以无操作方式为每个方法实现接口就可以了。

基本上,您需要评估每个案例的优点。

于 2010-08-29T17:16:46.643 回答
1

听起来您担心在事件发生时设置该布尔变量。如果用户覆盖eventHasOccurred(),则布尔变量将不会被设置并且isFinished()不会返回正确的值。为此,您可以使用一个抽象方法供用户重写以处理事件,另一种方法调用抽象方法并设置布尔值(请参见下面的代码示例)。

此外,您可以让需要处理事件的类扩展定义此方法的类(如下面的类),而不是将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

如果要将任何实现放在抽象基类中,它应该是使用无操作实现的子类的代码,因为这对基类也有意义。如果基类没有合理的实现(例如,如果您在此处讨论的方法没有合理的无操作),那么我建议将其保留为抽象。

关于重复的代码,如果有“家族”类都使用相同的方法实现,并且您不想在家族中的所有类之间复制代码,您可以简单地使用每个家族提供的帮助类这些实现。在您的示例中,传递事件的类的助手,类的助手接受并记录事件等。

于 2010-08-29T17:39:24.870 回答
1

当我创建与其他人一起开发的应用程序的基本大纲(类层次结构)时,我遇到了类似的情况。我选择放置方法抽象(并因此强制其实现)是出于通信目的。

基本上,其他队友不得不以某种方式显式地实现该方法,因此首先注意到它的存在,然后他们在那里返回的内容达成一致,即使它只是默认实现。

基类中的默认实现经常被忽略。

于 2010-08-30T19:09:22.673 回答
0

如果您要离开超类方法抽象,您可能需要考虑使用接口(不要与接口混淆)。因为接口是一个不提供具体实现的接口。

斯科特

扩展一下,当我们作为程序员被指示为一个经常是新的、有时是经验丰富的接口编写代码时,开发人员会错误地认为它引用了关键字接口,其中找不到实现细节。然而,更明确的说法是,任何顶级对象都可以被视为可以交互的接口。例如,一个名为 Animal 的抽象类将是一个接口,一个名为 Cat 的类将从 Animal 继承。

于 2010-08-29T17:28:04.520 回答