Ява: Не рекомендуется ли переопределять родительских детей? - PullRequest
0 голосов
/ 25 октября 2018

Мне было интересно, не осуждает ли это, что при разработке структуры, которая будет использоваться другими, класс имеет некоторую функцию в качестве поведения по умолчанию и ожидает, что его клиенты переопределят ее при необходимости.Примером может быть что-то вроде следующего:

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. У нас есть конвейерная структура, которая автоматически развертывает push-код, поэтому создание RecordProcessor абстрактного класса немедленно нарушит работу приложений наших клиентов.

Заставляет preProcess ничего не делать в родительском классе и позволять дочерним классам переопределять его, считая плохой практикой?Если нет, то каким должен быть лучший способ, чтобы клиенты знали, что теперь у них есть возможность предварительно обработать записи?Через Java документы?

Ответы [ 4 ]

0 голосов
/ 27 октября 2018

То, что я в итоге сделал - я тоже подумал, что это очень хорошо, вдохновленный @tsolakp, это просто создание дочернего класса для RecordProcessor, называемого что-то вроде PreprocessRecordProcessor.Это никак не мешает существующему коду, потому что ничего существующего не было затронуто.Класс должен выглядеть примерно так:

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

    protected abstract void preProcess();
}

И если клиенты этой библиотеки захотят добавить свою собственную логику, они могут просто расширить этот класс, и они будут вынуждены предоставить пред-процессная логика (как предполагается, с возможностью предоставить, что может привести к неожиданным результатам, если они забыли.)

0 голосов
/ 25 октября 2018

Предпочитаю композицию наследованию.Если вы хотите, чтобы ваши клиенты добавили пользовательскую предварительную обработку, сделайте это путем делегирования отдельным объектам.

    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
        }
    }
0 голосов
/ 26 октября 2018

Нет, переопределение не рекомендуется в Java.

  • Язык допускает переопределение .
  • Язык делает все методы переопределенными по умолчанию .
  • Библиотека классов Java содержит примеры того же шаблона.

Ваш подход является одним из разумных способов, позволяющих подклассам расширять поведение своего родительского класса.Есть альтернативы, такие как передача поведения как объекта.Однако единственно верного пути не существует.

Один из способов улучшить код - пометить preProcess () как protected .Это деталь реализации класса.Вам не нужно, чтобы кто-либо, имеющий RecordProcessor, решал, что он может вызывать preProcess () сам по себе, верно?

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

Еще один способ улучшить это - подумать, намерены ли вы создать экземпляр объекта.Суперкласс RecordProcessor.Если вы этого не сделаете, сделайте класс abstract , чтобы предотвратить это.Имя класса может выражать это, если хотите, или ваши правила кодирования требуют его.

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() { }
}
0 голосов
/ 25 октября 2018

Один из подходов состоит в том, чтобы пометить открытый метод как окончательный (но это также может нарушить работу существующих приложений) и разрешить переопределение методов защищенного хука.Например:

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