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

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

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

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 ), Каковы другие плюсы и минусы этих стратегий? Один из них лучше другого?

24
задан Scott 29 August 2010 в 17:11
поделиться

5 ответов

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

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

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

15
ответ дан 29 November 2019 в 00:22
поделиться

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

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

1
ответ дан 29 November 2019 в 00:22
поделиться

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

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

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

1
ответ дан 29 November 2019 в 00:22
поделиться

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

Скотт

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

0
ответ дан 29 November 2019 в 00:22
поделиться

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

EDIT (см. комментарии):

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);
  }
});
1
ответ дан 29 November 2019 в 00:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: