Преимущество непустого метода неабстрактного класса и абстрактного метода абстрактного метода?

#oop #abstract

#ооп #аннотация

Вопрос:

Я не понимаю, почему мы используем абстрактный метод (абстрактный класс), в то время как мы можем использовать пустой метод неабстрактного класса, а затем переопределяем его. Звучит ли это нормально? Я пытаюсь прояснить этот вопрос.

Я привожу 2 примера

 public abstract class MyClass {public abstract void foo();}
public MyChildClass extends MyClass {public void foo() {//..TODO}}

public class MyClass {public void foo(){//empty}}
public class MyChildClass extends MyClass {public void foo() {//..TODO}}
 

Какой из них лучше?

Комментарии:

1. Для начала, параметр abstract заставляет компилятор принудительно переопределять его, что очень удобно, когда кто-то другой (или вы сами) может забыть установить правильное переопределение. Неабстрактный вариант лучше для «необязательных» методов, таких как уведомления.

Ответ №1:

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

На самом низком уровне абстрактные методы дают вам две конкретные защиты во время компиляции:

  • Они заставляют вас переопределять метод в подклассе
  • Они запрещают создание абстрактного класса

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

Итак, когда бы вы использовали абстрактный класс? Вот несколько примеров:

Шаблонный метод

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

Например, если вы реализуете кэш, но политика аннулирования кэша является полиморфной, у вас может быть абстрактный invalidate() метод, который вызывается внутренне другими методами, но его реализация зависит от подклассов invalidate() .

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

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

Реализация других методов по умолчанию

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

Общий интерфейс и функциональность

Это просто более общая версия шаблона шаблона метода. Разница в том, что полиморфные методы являются частью API.

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

 interface File {
    abstract Buffer read(int size);
    abstract void write(Buffer buf);
    abstract long getSize();
    abstract void setSize();
    // ... get/set creation time, get/set modification time, get
    // file type etc.
    abstract long getOwner();
    abstract void setOwner(long owner);
}

abstract class AbstractFile extends File {
    DataMap dataMap;
    MetadataMap metaMap;
    protected getDiskMap() { return dataMap; }
    protected getMetaMap() { return metaMap; }
    public Buffer read(int size) { /* loop here */ }
    public void write(Buffer buf) { /* loop here */ }
    public long getSize() { /* logic */ }
    public void setSize() { /* logic */ }
    // ... implementation of get/set creation time, get/set modification
    // time, get file type etc.
}

abstract class HardDriveFile extends AbstractFile {
    OwnershipMap ownerMap;
    abstract long getOwner() { /* logic */ }
    abstract void setOwner(long owner) { /* logic */ }
}

abstract class ThumbDriveFile extends AbstractFile {
    // thumb drives have no ownership
    abstract long getOwner() { return 0; }
    abstract void setOwner(long owner) { /* no-op */ }
}

abstract class SomeOtherfile extends AbstractFile {
    ...
}
 

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

Наивным решением было бы объединить File and AbstractFile в один класс, где вы получили бы абстрактные методы getOwner() and setOwner() , но лучше скрыть абстрактные классы за реальными интерфейсами, чтобы предотвратить связь между потребителями API и абстрактным классом.