Чем интерфейс отличается от абстрактного класса
Перейти к содержимому

Чем интерфейс отличается от абстрактного класса

Зачем нужны интерфейсы и абстрактные классы

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

Интерфейсы

Давайте представим, что у нас имеется автомастерская WorkShop. Она может ремонтировать 3 вида автомобилей BMW, Mercedes и Porsche. В этом случае наш код будет выглядеть следующим образом:

void необходимо указывать в случаях, когда наш метод ничего не возвращает

Мы создали 3 класса для каждого автомобиля. У каждого класса имеются методы fixEngine и ChangeWheels. Каждый метод в каждом классе реализован по-разному для разных марок автомобилей. Основной класс WorkShop на вход принимает vehicle, который может быть 3 разных типов, поэтому мы указываем их через знак |.

В данном случае мы конкретно указываем тип автомобилей в конструкторе, которые принимает Workshop. Если бы мы этого не сделали, тогда бы мы не были уверены в том, что объект в переменной vehicle имеет методы fixEngine и changeWheels, что привело бы к ошибкам.

Но теперь возникает проблема — что будет, если наша мастерская через год начнет обслуживать дополнительно 10 типов автомобилей? В этом случае наш конструктор будет выглядеть как-то так:

Согласитесь, вариант не очень привлекательный. Однако решение есть! Мы можем использовать интерфейс Vehicle.

Интерфейс — это элемент, в котором можно объявить методы, которые должны быть реализованы в объекте.

Мы можем как имплементить(implements) интерфейс, так и указать его в качестве типа в аргументах конструктора Workshop. В случае с динамическими языками типа PHP единственным вариантом является использование implements(например, class BMW implements Vehicle; class Mercedes implements Vehicle и так далее). В JavaScript, например, интерфейсов нет вообще.

Теперь WorkShop может принимать любые объекты, у которых есть методы fixEngine и ChangeWheels. Ура! В этом заключается основная суть интерфейсов.

Абстрактный класс

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

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

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

Представим, что у нас есть два класса — принтер и сканер. В обоих случаях мы имеем повторяющийся код — одинаковые методы turnOn и Loading.

Как решить проблему? Можно создать класс Device, в котором реализуем методы turnOn и Loading, а зачем через наследование будем обращаться к нашим методам. Но в таком случае нам каждый раз нужно будет создавать сначала объект типа Device, а уже потом Printer и Scaner — неудобно. Можно использовать абстрактный класс Device.

Таким образом мы избавились от повторений в коде, и при этом нет необходимости создавать объект родительского класса Device (let device = new Device());

Абстрактные методы, например, полезны в случаях, если у нас в методе Loading, например, идет обращение к методу класса Scaner или Printer.

В примере в методе Loading мы вызываем метод cleanRAM, который в каждом случае имеет различную реализацию. Без указания абстрактного метода в классе Device TypeScript не понимает, что это за метод такой cleanRAM.

Таким образом, абстрактные классы и интерфейсы позволяют эффективно избавляться от повторений в коде. Делитесь мнением в комментариях и не забывайте подписываться на меня в Twitter.

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

Абстрактные классы и интерфейсы объединены мной в одну тему, так как по своей сути они очень похожи. И те и другие имеют отношение к “моделированию” классов. С их помощью мы можем показать, что у определённой группы классов есть что-то общее: то, что их отличает от всех остальных. Ключевая разница между ними лишь в том, как их применять.

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

Абстрактный класс — это класс, представляющий из себя “заготовку” для целого семейства классов, который описывает для них общий шаблон поведения. Такой класс не может быть создан. Т.е. нельзя создать его экземпляр. Он используются исключительно в качестве суперкласса, а его цель — моделирование поведения своего семейства, а также предоставление функционала для повторного использования. Другими словами абстрактный класс — это средство, для повторного использования кода.

Например, вы разрабатываете приложение, которое предоставляет информацию о деревьях. Тогда класс Tree должен быть абстрактным и объединять в себе свойства и функции, характерные для всех деревьев. А каждый наследник класса Tree будет по-своему их реализовывать.

Объявляется абстрактный класс при помощи ключевого слова abstract .

Наследование от такого класса осуществляется с помощью оператора : . При этом абстрактному классу не нужен модификатор open , потому что он “открыт” для наследования по умолчанию.

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

Каждый наследник обязан переопределять их все.

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

Так как этот компонент класса уже не будет абстрактным, наследники не смогут его переопределить.

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

У абстрактного класса может быть конструктор.

Тогда каждый наследник должен предоставить для него значения.

Интерфейсы

Интерфейс — это совокупность методов и правил, которые определяют поведение класса или общее поведение для группы независимых друг от друга классов. Интерфейсы похожи на абстрактные классы тем, что нельзя создать их экземпляры и они могут определять абстрактные или конкретные функции и свойства. Отличие в том, что интерфейсу не важна связь “родитель-наследник”, он задаёт лишь правила поведения.

К примеру, в нашем приложении уже есть целое семейство деревьев, информацию о которых можно с лёгкостью получить — их название и краткое описание. Но помимо этого деревья могут цвести и производить кислород. Такие методы можно добавить в абстрактный класс Tree , но что если мы решим помимо деревьев предоставлять информацию о, скажем, водорослях. У них тоже есть период цветения и они тоже производят кислород, но они не являются деревьями, а значит мы не можем наследовать их от класса Tree . Таким образом мы получили группу независимых друг от друга классов с одинаковым поведением, которое будет реализовываться через интерфейс Cultivable .

Объявляется интерфейс ключевым словом interface .

Реализация интерфейса осуществляется аналогично наследованию: после имени класса ставится оператор : и название интерфейса.

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

Класс должен реализовывать все абстрактные свойства и функции, определённые в интерфейсе.

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

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

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

Каждый класс, реализующий интерфейс Cultivable может использовать свойства и функции интерфейса Fruitable , если в этом есть необходимость.

Шпаргалка: абстрактный класс или интерфейс?

  • У вас есть семейство классов, из которых можно выделить общую сущность? Определите эту сущность в качестве абстрактного класса и она будет “заготовкой” для всего семейства.
  • Вам нужно создать более конкретную версию класса? Создайте подкласс этого класса и добавьте недостающее поведение.
  • Требует определить общее поведение для группы независимых друг от друга классов? Создайте интерфейс и реализуйте его теми классами, которым необходимо это поведение.

Ключевые моменты

  • Абстрактный класс — это “заготовка” для целого семейства классов.
  • Нельзя создать экземпляр абстрактного класса.
  • Абстрактный класс может содержать как абстрактные, так и конкретные реализации свойств и функций.
  • Класс, который содержит абстрактное свойство или функцию, должен быть объявлен абстрактным.
  • Абстрактный класс может быть без единого абстрактного свойства или функции.
  • У класса может быть только один суперкласс.
  • Наследники абстрактного класса должны переопределять все его абстрактные свойства и функции.
  • Чтобы наследники могли переопределять конкретные реализации свойств и функций, для них в абстрактном классе должен быть явно указан модификатор open .
  • У абстрактного класса может быть конструктор.
  • Интерфейс определяет поведение класса или общее поведение для группы независимых друг от друга классов.
  • Нельзя создать экземпляр интерфейса.
  • Интерфейс может содержать как абстрактные, так и конкретные реализации функций.
  • Свойства интерфейсов могут быть абстрактными, а могут иметь get() методы.
  • Класс может реализовывать несколько интерфейсов.
  • Класс должен реализовывать все абстрактные свойства и функции, определённые в интерфейсе.
  • Если интерфейс реализовывается абстрактным классом, то переопределение его абстрактных свойств и функций может быть передана наследникам абстрактного класса.
  • Интерфейс может реализовывать другой интерфейс.

Полезные ссылки

Abstract classes — официальная документация.
Абстрактные классы — перевод на русский (об абстрактных классах в самом низу статьи).
Interfaces — официальная документация.
Интерфейсы — перевод статьи про интерфейсы на русский.

Отличия абстрактного класса от интерфейса?

Все упирается в понятие «тип». В былые времена, то есть во времена языка Simula, из которого черпали вдохновение создатели C++, были только классы. И на классах базировалась система типов. Причем механизм наследования был реализован так, как реализован, исключительно для экономии памяти, которая в те времена была очень дорогой.

Для того чтобы достичь полиморфизма, мы должны иметь возможность объявлять абстрактные типы. Мол «любая хрень которая имеет такой тип будет работать как надо». Потому в языках типа C++ появились абстрактные классы. Поскольку иногда нам хочется делать композицию абстрактных типов, в C++ реализовали множественное наследование.

В Java, которая во многом черпала вдохновения из C++ и smalltalk, решили ввести еще одну сущность — интерфейсы. Это был своего рода упрощенный способ задать абстрактный базовый тип. По итогу чтобы не решать проблему бриллианта (или ромба) от множественного наследования было решено отказаться и дать возможность классам имплементить несколько интерфейсов.

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *