默认实现还是抽象方法?

是将方法的默认实现放在超类中,并在子类想要偏离此值时重写它,还是应该将超类方法抽象出来,并在许多子类中重复正常实现?

例如,我参与的一个项目有一个类,用于指定它应停止的条件。抽象类如下所示:

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;
    }
}

我们应该如何在抽象超类中最好地表示?大多数子类可以具有此方法的no-op实现(例如),而有些子类需要重要的实现才能正常运行(例如),而其他子类不需要对消息执行任何操作,但必须将其传递给其下属,以便它们能够正确操作(例如)。eventHasOccurred(Event e)AlwaysHaltingConditionHaltAfterAnyEventHaltingConditionConjunctionHaltingCondition

我们可以有一个默认的实现,这将减少代码重复,但如果它没有被覆盖,会导致一些子类编译但无法正常运行,或者我们可以将方法声明为抽象,这将要求每个子类的作者考虑他们提供的实现,尽管十次中有九次它是no-op实现。这些策略的其他优缺点是什么?一个比另一个好得多吗?


答案 1

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

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

基本上,您需要根据其优点来评估每个案例。


答案 2

如果要将任何实现放在抽象基类中,则它应该是使用no-op实现的子类的代码,因为这种实现对基类也有意义。如果基类没有合理的实现(例如,如果你在这里讨论的方法没有合理的no-op),那么我建议把它抽象出来。

对于重复的代码,如果存在都使用相同方法实现的类的“系列”,并且您不希望在系列中的所有类中复制代码,则可以简单地使用提供这些实现的每个系列的帮助器类。在您的示例中,传递事件的类的帮助程序、接受并记录事件的类的帮助程序等。