Подтвердить что ты не робот

Реализация по умолчанию или абстрактный метод?

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

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

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) в абстрактном суперклассе? Большинство подклассов могут иметь не-op реализацию этого метода (например, AlwaysHaltingCondition), в то время как для некоторых требуется значительная реализация для правильной работы (например, HaltAfterAnyEventHaltingCondition), а другим ничего не нужно делать с сообщением, но нужно передать его на своих подчиненных, чтобы они работали правильно (например, ConjunctionHaltingCondition).

У нас может быть реализация по умолчанию, которая уменьшит дублирование кода, но приведет к компиляции некоторых подклассов, но не будет корректно работать, если она не была переопределена, или мы могли бы объявить ее абстрактным, что потребовало бы автора каждый подкласс, чтобы подумать о реализации, которую они предоставляют, хотя девять раз из десяти он был бы не-op-реализацией. Каковы другие плюсы и минусы этих стратегий? Является ли это намного лучше, чем другой?

4b9b3361

Ответ 1

Один из вариантов состоит в том, чтобы иметь еще один абстрактный подкласс для использования в качестве суперкласса для всех реализаций, которые хотят использовать реализацию по умолчанию.

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

Вы должны оценивать каждый случай по существу, в основном.

Ответ 2

Похоже, вы обеспокоены настройкой этой логической переменной, когда происходит событие. Если пользователь переопределяет 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);
  }
});

Ответ 3

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

Что касается дублированного кода, если существуют "семейства" классов, которые используют одну и ту же реализацию метода, и вы не хотите дублировать код по всем классам в семье, вы можете просто использовать вспомогательные классы в семьи, которые поставляют эти реализации. В вашем примере помощник для классов, которые пропускают события, помощник для классов принимает и записывает событие и т.д.

Ответ 4

Я столкнулся с похожим сценарием, когда создал основной контур (иерархию классов) приложения, которое я разрабатывал вместе с другими на работе. Мой выбор для размещения метода abstract (и, следовательно, для его принудительной реализации) был для целей связи.

В принципе, другие товарищи по команде каким-то образом явно реализовали этот метод и поэтому прежде всего отмечают его присутствие и второй согласны на то, что они возвращают туда, даже если это просто реализация по умолчанию.

Стандартные реализации в базовых классах часто пропускаются.

Ответ 5

Если вы оставляете абзац метода суперкласса, вам может понадобиться изучить интерфейс (не путать с интерфейсом). Поскольку интерфейс - это тот, который не обеспечивает конкретной реализации.

Скотт

Чтобы расширить, когда мы, как программисты, наставляем код на интерфейс, часто возникающий как новый, а иногда и опытный, разработчики ошибочно предполагают, что он относится к ключевому слову Interface, в котором не могут быть найдены детали реализации. Тем не менее, более ясный способ сказать это, что любой объект верхнего уровня можно рассматривать как интерфейс, с которым можно взаимодействовать. Например, абстрактным классом, называемым Animal, был бы интерфейс, называемый классом Cat, который наследует Animal.