Абстрактные классы и интерфейсы

Абстрактные классы и интерфейсы

Дорогу осилит идущий




ВАЖНО: ниже находится устаревшая версия статьи. Актуальную можно найти здесь: ссылка






Абстрактные классы и ключевое слово abstract

Знакомство с данной темой предлагаю начать со статьи: https://metanit.com/java/tutorial/3.6.php

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

Немного об abstract:

  • abstract применим лишь к классам и методам. Абстрактных полей или конструкторов не бывает;
  • Использование abstract в отношении класса говорит о том, что создать объекты этого класса невозможно. Лишь объекты его наследников;
  • abstract перед классом не обязует создавать абстрактных методов в нем. Другой вопрос, насколько имеет смысл абстрактный класс без абстрактных методов;
  • Технически, абстрактный класс может быть наследником не абстрактного (не говоря об Object). Но это показатель плохо спроектированной системы;
  • Абстрактных методов в абстрактном классе может быть неограниченное количество;
  • Если наследник не переопределяет все абстрактные методы предка — наследник тоже должен быть абстрактным классом.

А также о концептуальных нюансах:

  • Стоит ли делать класс абстрактным — зависит от конкретной ИС. Одни и те же сущности могут быть по-разному представлены в разных ИС, это зависит лишь от того, как спроектирована система. Если вспомним задачу 2 из темы наследование, кажется логичным сделать Animal абстрактным классом. Однако в задаче 1 из того же урока такой необходимости нет;
  • Абстрактные классы редко применяются к классам-сущностям. Еще реже в таких случаях используются абстрактные методы.

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


Интерфейсы

Рекомендую ознакомиться со статьей: https://metanit.com/java/tutorial/3.7.php

Она достаточно объемна, но ее стоит изучить досконально, кроме, пожалуй, раздела «Вложенные интерфейсы».

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

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

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

Именно так дело обстоит с точки зрения концепции абстрактных классов и интерфейсов в Java. На практике область применения интерфейсов шире:

  • Интерфейс может быть декларацией поведения для абстрактного класса. И тогда иерархию наследования можно рассматривать как interfaceabstract classsubclass. Такой сценарий можно встретить достаточно часто в классах бизнес-логики, реже — в классах сущностей;
  • Интерфейс может быть просто маркером, демонстрирующим, что конечные классы имеют определенные особенности или возможности. Методов в таком интерфейсе вообще не будет. В дальнейшем мы встретим несколько примеров этого в Java. Возможно, и сами реализуем подобный механизм в задаче, где это необходимо;
  • Интерфейсы бывают функциональными. С этим мы будем разбираться отдельно в контексте функционального программирования в Java, точнее, разбирая роль лямбда-выражений в нем;
  • Иногда интерфейсы необходимы, чтобы реализовать множественное наследование. Напомню, мы не можем наследоваться от нескольких классов. А реализовывать несколько интерфейсов (в т.ч. одновременно с наследованием класса) — можем. Такое стало возможно после появления дефолтных реализаций методов интерфейсов в Java 8. Почти всегда такое применение будет ошибкой проектирования, но есть вероятность, что вы с этим столкнетесь в будущем;
  • Иногда задача интерфейса — действительно указать на поведение в прямом смысле. Чаще всего, это разного рода обработчики (кнопок, сообщений, чего угодно). В таком случае каждая реализация интерфейса будет обрабатывать свой тип кнопок/сообщений/чего угодно. Достаточно популярный механизм вне зависимости от направления разработки. Такое можно встретить и в desktop, и в mobile, и в backend. Вероятно, мы рассмотрим этот подход позже, в контексте паттернов проектирования.

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

  • Абстрактный класс – это сущность, интерфейс – это контракт (декларация поведения). Сущность расширяется, контракт реализовывается. Расширить можно только один класс, реализовать - несколько интерфейсов;
  • Абстрактный класс – это класс, у которого не реализован один или несколько методов
  • Интерфейс – не содержит реализации ни одного метода (до Java 8);
  • Методы интерфейса public или default (с Java 9 интерфейс может содержать и private методы). Методы абстрактного класса могут иметь любые модификаторы доступа;
  • Поля интерфейса всегда public static final, пусть явно это и не указывается. Поля абстрактного класса – любые.

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

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


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


С теорией на этом все!


Переходим к практике:

Задача 1:

Реализуйте задачу https://github.com/KFalcon2022/practical-tasks/tree/master/src/com/walking/lesson14_polymorphism/task1

Вариант 1: используя абстрактный класс;

Вариант 2: используя интерфейс.


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


Задача 2:

Реализуйте задачу https://github.com/KFalcon2022/practical-tasks/blob/master/src/com/walking/lesson3_casts_conditional_constructions/Task2SwitchCase.java

с использованием интерфейсов. Каждая реализация должна возвращать свое сообщение.


Задача 3:

Реализуйте любую задачу из уроков о наследовании или полиморфизме с использованием новых знаний. Выбирайте инструмент с умом.


Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)

Канал: https://t.me/+relA0-qlUYAxZjI6

Мой тг: https://t.me/ironicMotherfucker

 

Дорогу осилит идущий!

Report Page