[英]Default implementation or abstract method?
将方法的默认实现放在超类中更好,并在子类想要偏离它时重写它,或者你应该只留下超类方法摘要,并在许多子类中重复正常实现?
例如,我参与的项目有一个类,用于指定它应该停止的条件。 抽象类如下:
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
)。
我们可以有一个默认实现,它可以减少代码重复,但会导致一些子类编译但如果没有被覆盖则不能正常运行,或者我们可以将方法声明为abstract,这将需要每个子类的作者考虑一下它们提供的实现,尽管十分之九是无操作实现。 这些策略的其他优缺点是什么? 一个比另一个好多少?
一种选择是有另一个抽象类,作为超对于这确实想使用的默认实现全部实现使用。
就个人而言,我通常将抽象类中的非最终方法抽象(或者仅使用接口),但它肯定取决于具体情况。 例如,如果你有一个包含许多方法的接口,并且你希望能够选择加入其中一些方法,那么一个抽象类就可以为每个方法以无操作的方式实现接口。
基本上,你需要根据其优点来评估每个案例。
听起来你担心在事件发生时设置该布尔变量。 如果用户重写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);
}
});
如果要将任何实现放在抽象基类中,它应该是使用no-op实现的子类的代码,因为这是一个对基类也有意义的实现。 如果基类没有合理的实现(例如,如果你在这里讨论的方法没有明智的无操作),那么我建议将它留作抽象。
对于重复的代码,如果存在所有使用相同方法实现的类的“族”,并且您不希望在该族中的所有类中复制代码,则可以简单地使用每个族的帮助程序类。这些实现。 在您的示例中,传递事件的类的助手,类的助手接受并记录事件等。
当我创建与工作中的其他人一起开发的应用程序的基本大纲(类层次结构)时,我遇到了类似的情况。 我选择放置方法摘要 (并因此强制其实现)是出于通信目的。
基本上,其他队友已经以某种方式明确地实现了该方法,因此首先注意到它的存在,并且第二个同意他们返回那里,即使它只是默认实现。
基类中的默认实现经常被忽略。
如果您要离开超类方法摘要,您可能需要考虑使用接口(不要与接口混淆)。 因为接口是不提供具体实现的接口。
斯科特
为了扩展,当我们作为程序员被指示经常对新的,有时经验丰富的接口进行编码时,开发人员会错误地认为它是在引用关键字接口,其中没有找到实现细节。 然而,更明确的说法是任何顶级对象都可以被视为可以与之交互的界面。 例如,名为Animal的抽象类将是一个名为Cat的类,它将继承自Animal。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.