Рубрики
Без рубрики

Абстрактные классы в Java

Узнайте, как и когда использовать абстрактные классы как часть иерархии классов в Java.

Автор оригинала: Alejandro Ugarte.

1. Обзор

Есть много случаев при реализации контракта, когда мы хотим отложить некоторые части реализации, чтобы завершить их позже. Мы можем легко сделать это в Java с помощью абстрактных классов.

В этом уроке мы изучим основы абстрактных классов в Java и в каких случаях они могут быть полезны .

2. Ключевые понятия для абстрактных классов

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

  • Мы определяем абстрактный класс с помощью абстрактный модификатор, предшествующий класс ключевое слово
  • Абстрактный класс может быть подклассом, но он не может быть создан
  • Если класс определяет один или несколько методов abstract , то сам класс должен быть объявлен abstract
  • Абстрактный класс может объявлять как абстрактные, так и конкретные методы
  • Подкласс, производный от абстрактного класса, должен либо реализовывать все абстрактные методы базового класса, либо сам быть абстрактным

Чтобы лучше понять эти концепции, мы приведем простой пример.

Пусть наш базовый абстрактный класс определит абстрактный API настольной игры:

public abstract class BoardGame {

    //... field declarations, constructors

    public abstract void play();

    //... concrete methods
}

Затем мы можем создать подкласс, реализующий метод play :

public class Checkers extends BoardGame {

    public void play() {
        //... implementation
    }
}

3. Когда следует использовать абстрактные классы

Теперь, давайте проанализируем несколько типичных сценариев, в которых мы должны предпочесть абстрактные классы интерфейсам и конкретным классам:

  • Мы хотим инкапсулировать некоторые общие функции в одном месте (повторное использование кода), которые будут совместно использоваться несколькими связанными подклассами
  • Нам нужно частично определить API, который наши подклассы могут легко расширить и усовершенствовать
  • Подклассы должны наследовать один или несколько общих методов или полей с модификаторами защищенного доступа

Давайте иметь в виду, что все эти сценарии являются хорошими примерами полного, основанного на наследовании соблюдения принципа Open/Closed .

Более того, поскольку использование абстрактных классов неявно имеет дело с базовыми типами и подтипами, мы также используем преимущества полиморфизма .

Обратите внимание, что повторное использование кода является очень веской причиной для использования абстрактных классов, пока сохраняется связь “is-a” в иерархии классов.

И Java 8 добавляет еще одну морщину с методами по умолчанию , что иногда может заменить необходимость создания абстрактного класса в целом.

4. Пример Иерархии считывателей файлов

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

4.1. Определение базового абстрактного класса

Итак, если бы мы хотели иметь несколько типов считывателей файлов, мы могли бы создать абстрактный класс, который инкапсулирует то, что является общим для чтения файлов:

public abstract class BaseFileReader {
    
    protected Path filePath;
    
    protected BaseFileReader(Path filePath) {
        this.filePath = filePath;
    }
    
    public Path getFilePath() {
        return filePath;
    }
    
    public List readFile() throws IOException {
        return Files.lines(filePath)
          .map(this::mapFileLine).collect(Collectors.toList());
    }
    
    protected abstract String mapFileLine(String line);
}

Обратите внимание, что мы сделали filePath защищенным, чтобы подклассы могли получить к нему доступ в случае необходимости. Что еще более важно, мы оставили кое-что незаконченным: как на самом деле разобрать строку текста из содержимого файла.

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

На первый взгляд, BaseFileReader может показаться ненужным. Тем не менее, это основа чистого, легко расширяемого дизайна. С его помощью мы можем легко реализовать различные версии программы чтения файлов, которые могут сосредоточиться на их уникальной бизнес-логике .

4.2. Определение Подклассов

Естественная реализация-это, вероятно, та, которая преобразует содержимое файла в нижний регистр:

public class LowercaseFileReader extends BaseFileReader {

    public LowercaseFileReader(Path filePath) {
        super(filePath);
    }

    @Override
    public String mapFileLine(String line) {
        return line.toLowerCase();
    }   
}

Или другой может быть тот, который преобразует содержимое файла в верхний регистр:

public class UppercaseFileReader extends BaseFileReader {

    public UppercaseFileReader(Path filePath) {
        super(filePath);
    }

    @Override
    public String mapFileLine(String line) {
        return line.toUpperCase();
    }
}

Как мы видим из этого простого примера, каждый подкласс может сосредоточиться на своем уникальном поведении без необходимости указывать другие аспекты чтения файлов.

4.3. Использование подкласса

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

@Test
public void givenLowercaseFileReaderInstance_whenCalledreadFile_thenCorrect() throws Exception {
    URL location = getClass().getClassLoader().getResource("files/test.txt")
    Path path = Paths.get(location.toURI());
    BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);
        
    assertThat(lowercaseFileReader.readFile()).isInstanceOf(List.class);
}

Для простоты целевой файл находится в папке src/main/resources/files . Поэтому мы использовали загрузчик классов приложений для получения пути к файлу примера. Не стесняйтесь проверить наш учебник по загрузчикам классов в Java .

5. Заключение

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

Как обычно, все примеры кода, показанные в этом руководстве, доступны на GitHub .