Многие младшие разработчики ошибочно считают интерфейсы, абстрактные и конкретные классы незначительными вариациями одного и того же и выбирают один из них исключительно по техническим причинам: Нужно ли множественное наследование? Нужно ли какое-то место, чтобы поставить общие методы? Нужно ли беспокоиться о чем-то другом, кроме как о конкретном классе? Это неправильно, и в этих вопросах скрыта основная проблема: "Я" . Когда вы пишете код для себя, вы редко думаете о других настоящих или будущих разработчиках, работающих над вашим кодом или над ним.
Интерфейсы и абстрактные классы, хотя и кажутся с технической точки зрения сходными, имеют совершенно разные значения и цели.
Резюме
Интерфейс определяет контракт , который некоторая реализация выполнит для вас .
Абстрактный класс обеспечивает поведение по умолчанию , которое ваша реализация может использовать повторно.
Альтернативное резюме
- Интерфейс для определения общедоступных API
- Абстрактный класс для внутреннего использования и для определения SPI
О важности сокрытия деталей реализации
Конкретный класс выполняет реальную работу очень специфическим образом. Например, ArrayList
использует непрерывную область памяти для компактного хранения списка объектов, который предлагает быстрый произвольный доступ, итерации и изменения на месте, но ужасен при вставках, удалениях и иногда даже добавлениях; Между тем, LinkedList
использует узлы с двойной связью для хранения списка объектов, который вместо этого предлагает быструю итерацию, изменения на месте и вставку / удаление / добавление, но ужасен при произвольном доступе. Эти два типа списков оптимизированы для разных вариантов использования, и очень важно, как вы собираетесь их использовать. Когда вы пытаетесь выжать производительность из списка, с которым вы интенсивно взаимодействуете, и когда вы выбираете тип списка, зависит от вас, вы должны тщательно выбрать, какой именно экземпляр вы создаете.
С другой стороны, пользователям высокого уровня списка на самом деле все равно, как он реализован на самом деле, и они должны быть изолированы от этих деталей. Давайте представим, что Java не предоставляла интерфейс List
, а имела только конкретный класс List
, который на самом деле и есть LinkedList
прямо сейчас. Все Java-разработчики приспособили бы свой код к деталям реализации: избегайте произвольного доступа, добавьте кеш для ускорения доступа или просто переопределите ArrayList
самостоятельно, хотя это было бы несовместимо со всем другим кодом, который фактически работает с List
только. Это было бы ужасно ... Но теперь представьте, что мастера Java на самом деле понимают, что связанный список ужасен для большинства реальных случаев использования, и решили переключиться на список массивов для своего единственного доступного класса List
. Это повлияет на производительность каждой Java-программы в мире, и люди не будут этому рады. И главный виновник в том, что детали реализации были доступны, и разработчики предположили, что эти детали являются постоянным контрактом, на который они могут положиться. Вот почему важно скрывать детали реализации и определять только абстрактный контракт. Это цель интерфейса: определить, какие входные данные принимает метод и какой ожидаемый результат, не подвергая всех смелости, которая побудит программистов настроить свой код, чтобы соответствовать внутренним деталям, которые могут измениться при любом будущем обновлении .
Абстрактный класс находится посередине между интерфейсами и конкретными классами. Предполагается, что реализации помогают использовать общий или скучный код. Например, AbstractCollection
предоставляет базовые реализации для isEmpty
на основе размера, равного 0, contains
в качестве итерации и сравнения, addAll
в качестве повторного add
и т. Д. Это позволяет реализациям сосредоточиться на важнейших частях, которые различают их: как на самом деле хранить и получать данные.
API против SPI
Интерфейсы с низкой когезией шлюзы между различными частями кода. Они позволяют библиотекам существовать и развиваться, не нарушая каждый пользователь библиотеки, когда что-то меняется внутри. Он называется Прикладное программирование Интерфейс , а не Классы прикладного программирования. В меньшем масштабе они также позволяют нескольким разработчикам успешно сотрудничать в крупномасштабных проектах, разделяя различные модули через хорошо документированные интерфейсы.
Абстрактные классы являются хелперами , которые используются при реализации интерфейса, предполагая некоторый уровень детализации реализации. В качестве альтернативы абстрактные классы используются для определения SPI, интерфейсов поставщиков услуг.
Разница между API и SPI незначительна, но важна: для API основное внимание уделяется тому, кто использует , а для SPI основное внимание уделяется тому, кто реализует * 1075. * это.
Добавление методов в API легко, все существующие пользователи API все равно будут компилироваться. Добавить методы в SPI сложно, поскольку каждый поставщик услуг (конкретная реализация) должен будет внедрить новые методы. Если для определения SPI используются интерфейсы, поставщик должен будет выпускать новую версию при каждом изменении контракта SPI. Если вместо этого используются абстрактные классы, новые методы могут быть определены в терминах существующих абстрактных методов или в виде пустых заглушек throw not implemented exception
, которые по крайней мере позволят более старой версии реализации службы по-прежнему компилироваться и запускаться.
Примечание по Java 8 и методы по умолчанию
Хотя в Java 8 были введены методы по умолчанию для интерфейсов, что делает грань между интерфейсами и абстрактными классами еще более размытой, это было сделано не для того, чтобы реализации могли повторно использовать код, а чтобы было проще менять интерфейсы, которые служат как API, так и как SPI (или неправильно используются для определения SPI вместо абстрактных классов).
Какой использовать?
- Предполагается, что вещь будет публично использоваться другими частями кода или другим внешним кодом? Добавьте интерфейс, чтобы скрыть детали реализации от публичного абстрактного контракта, который является общим поведением вещи.
- Является ли вещь чем-то, что должно иметь несколько реализаций с большим количеством общего кода? Сделайте и интерфейс и абстрактную, неполную реализацию.
- Будет ли когда-нибудь только одна реализация, и никто другой не будет ее использовать? Просто сделайте это конкретным классом.
- «когда-либо» - это долго, вы можете быть осторожны и все равно добавить интерфейс поверх него.
Вывод: обратное часто делается неправильно: при использовании вещь всегда старайтесь использовать самый универсальный класс / интерфейс, который вам действительно нужен. Другими словами, не объявляйте ваши переменные как ArrayList theList = new ArrayList()
, если только у вас на самом деле нет сильной зависимости от того, что он является списком array , и никакие другие типы списков не обрезают его для вас. Вместо этого используйте List theList = new ArrayList
или даже Collection theCollection = new ArrayList
, если факт, что это список, а не какой-либо другой тип коллекции, на самом деле не имеет значения.