Java:劝阻孩子优先于父母吗?

时间:2018-10-25 17:38:32

标签: java inheritance polymorphism override

我想知道是否在设计供其他人使用的框架时会不赞成,一类具有某些功能作为默认行为,并期望其客户在必要时覆盖它。一个示例如下所示:

public class RecordProcessor<T extends Record> {
    // ...
    public void process() {
        // process record logic
    }
}

该库的用户创建其具体类以处理自己的T类型的记录。

现在,我想添加一个名为preProcess()的函数,以使使用者能够预处理其记录。然后看起来像这样:

public class RecordProcessor<T extends Record> {
    // ...
    public void process() {
        preprocess();
        // process record logic
    }

    public void preProcess() {
        // By default no preprocessing
    }
}

我知道我可以使preProcess成为抽象函数,但是由于两个原因,我不想这样做:

  1. 并非所有客户都需要预处理他们的记录

  2. 我们有一个自动部署推送代码的管道结构,因此将RecordProcessor设为抽象类将立即破坏客户的应用程序。

preProcess在父类中什么也不做,而让子类覆盖它是不好的做法吗?如果不是,最好的方法是让客户知道他们现在可以预处理记录了吗?通过Java文档?

4 个答案:

答案 0 :(得分:3)

一种方法是将公共方法标记为最终方法(但这也可能会破坏现有应用程序),并允许覆盖受保护的钩子方法。例如:

public class RecordProcessor<T extends Record> {
    // ...
    public final void process() {
        doPreProcess();
        doProcess();
        doPostProcess();
    }

    protected void doPreProcess() {
        // By default no preprocessing
        return;
    }

    protected void doProcess() {
        // some default implementation
    }

    protected void doPostProcess() {
       // By default no postprocessing
       return;
    }
}

拥有一些文档应该使其他开发人员很自然地认识到可选的扩展方法。

使用什么也不做的钩子方法,我没有发现任何问题。但是,它应该包含return语句,以便静态分析工具不会抱怨。

更新:为了避免破坏现有应用程序,请尽可能将现有方法标记为不推荐使用,并引入一种新方法。例如:

public class RecordProcessor<T extends Record> {
    // ...

    public final void execute() {
        doPreProcess();
        doProcess();
        doPostProcess();
    }

    @Deprecated - use execute() method instead.
    public void process() {
        doProcess();
    }

    protected void doPreProcess() {
        // By default no preprocessing
        return;
    }

    protected void doProcess() {
        // some default implementation
    }

    protected void doPostProcess() {
       // By default no postprocessing
       return;
    }
}

答案 1 :(得分:2)

优先考虑组成而不是继承。如果希望客户添加自定义预处理,则可以通过委派给单独的对象来进行。

    public interface RecordPreProcessor<T extends Record>{
        public void process(T record);
    }

    public class RecordProcessor<T extends Record> {
        private RecordPreProcessor<T> recordPreProcessor = null;         

        public void setRecordPreProcessor(RecordPreProcessor<T> recordPreProcessor) {
            this.recordPreProcessor = recordPreProcessor;
        }

        public void process() {
            if (recordPreProcessor != null) recordPreProcessor.process(record);
            // process record logic
        }
    }

答案 2 :(得分:0)

否,在Java中不建议重写。

  • 允许语言覆盖。
  • 该语言使所有方法在默认情况下均可覆盖
  • Java类库包含相同模式的示例。

您的方法是一种允许子类扩展其父类行为的合理方法。还有其他选择,例如将行为作为对象传递。但是,没有一种真正的方法。

可以改善代码的一种方法是将preProcess()标记为受保护的。这是该类的实现细节。您不希望任何人拥有RecordProcessor来决定他们可以自己调用preProcess(),对吗?

public class RecordProcessor<T extends Record> {
  ... 
  protected void preProcess() {
  ^^^^^^^^^
    // By default no preprocessing
  }
}

另一种改进此方法的方法是考虑是否打算让任何人创建超类RecordProcessor的实例。如果您不这样做,请使该类为抽象,以防止出现这种情况。如果您愿意,可以使用类名来表示,或者您的编码指南也可以要求。

public abstract class AbstractRecordProcessor<T extends Record> {
       ^^^^^^^^       ^^^^^^^^
  ... 
  protected void preProcess() {
    // By default no preprocessing
  }
}

记录此类方法的一种常用方法是使用短语“默认实现不执行任何操作。子类可以覆盖此方法...” 。例如,下面是java.util.concurrent.FutureTask.done()的文档。您可以通过在线搜索该词组的第一句话来找到更多示例。

public class FutureTask<V> implements RunnableFuture<V> {
  ...
  /**
   * Protected method invoked when this task transitions to state
   * {@code isDone} (whether normally or via cancellation). The
   * default implementation does nothing.  Subclasses may override
   * this method to invoke completion callbacks or perform
   * bookkeeping. Note that you can query status inside the
   * implementation of this method to determine whether this task
   * has been cancelled.
   */
  protected void done() { }
}

答案 3 :(得分:0)

我最终做的是-在@tsolakp的启发下,我认为还不错,它只是创建了RecordProcessor的子类,称为PreprocessRecordProcessor之类。这没有办法干扰现有的代码,因为没有涉及到现有的代码。该类将是这样的:

public class PreprocessRecordProcessor<T extends Record> extends RecordProcessor<T> {
    // ...
    public void process() {
        preProcess();
        super.process();
    }

    protected abstract void preProcess();
}

如果该库的客户想要添加自己的逻辑,则可以简单地扩展此类,并且可以强制提供预处理逻辑(假定可以选择提供,如果他们忘记了,可能会导致意外结果。)