将方法的默认实现放在超类中是否更好,并在子类想要偏离它时重写它,或者你应该将超类方法保留为抽象,并在许多子类中重复正常的实现? / p>
例如,我参与的项目有一个类,用于指定它应该停止的条件。抽象类如下:
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,这将需要作者每个子类都要考虑它们提供的实现,尽管十分之九是无操作实现。这些策略的其他优缺点是什么?一个比另一个好多了吗?
答案 0 :(得分:13)
一个选项是拥有另一个抽象子类,用作做想要使用默认实现的所有实现的超类。
我个人通常在抽象类中留下非最终方法abstract(或者只是使用接口),但它肯定取决于具体情况。如果您有一个包含许多方法的接口,并且您希望能够只选择加入某些,那么就是一个抽象类,它以无操作的方式为每个方法实现接口方法很好。
你需要根据其优点来评估每个案例,基本上。
答案 1 :(得分: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);
}
});
答案 2 :(得分:1)
如果要将任何实现放在抽象基类中,它应该是使用no-op实现的子类的代码,因为这是一个对基类也有意义的实现。如果基类没有合理的实现(例如,如果你在这里讨论的方法没有明智的无操作),那么我建议将它留下来。
对于重复的代码,如果所有类的“族”都使用该方法的相同实现,并且您不希望在该族中的所有类中复制代码,则可能只需使用辅助类提供这些实现的家庭。在您的示例中,用于传递事件的类的助手,用于类的助手接受并记录事件等。
答案 3 :(得分:1)
当我创建与工作中的其他人一起开发的应用程序的基本大纲(类层次结构)时,我遇到了类似的情况。我选择放置方法抽象(并因此强制其实施)是为了沟通目的。
基本上,其他队友已经以某种方式明确地实施了该方法,因此首先注意到它的存在,并且第二个同意关于他们返回的内容,即使它只是默认实现。
基类中的默认实现经常被忽略。
答案 4 :(得分:0)
如果要离开超类方法摘要,您可能需要考虑使用接口(不要与接口混淆)。因为接口是不提供具体实现的接口。
<强> 斯科特 强>
要扩展,当我们作为程序员被指示经常对新的,有时经验丰富的接口进行编码时,开发人员会错误地认为它是在引用关键字接口,其中没有找到实现细节。然而,更明确的说法是任何顶级对象都可以被视为可以与之交互的界面。例如,名为Animal的抽象类将是一个名为Cat的类,它将继承自Animal。