Найти тему
ZDG

ООП: Интерфейсы

Эта статья продолжает цикл материалов об объектно-ориентированном программировании. Если вы ещё не ознакомились с введением в ООП, следует это сделать.

В реальной жизни мы часто встречаем интерфейсы. Само слово "интерфейс" переводится как междумордие взаимодействие, или соприкосновение, то есть это точка соприкосновения. Например, интерфейс "Транспортное Средство" предназначен для взаимодействия (соприкосновения) водителя и транспортного средства. Что для этого нужно? Условно говоря, педаль "газ", педаль "тормоз" и руль. Стало быть, интерфейс "Транспортное Средство" объявляет всем – мне всё равно кто вы, но если вы хотите считаться транспортным средством, то вы должны иметь методы "вперед", "стоп" и "поворот".

И вот самые разные транспортные средства – такие как автомобиль, мотоцикл, экскаватор, подводная лодка, космический корабль – объявляют, что они применяют интерфейс "Транспортное средство", а значит любой, кто умеет пользоваться этим интерфейсом, может управлять ими через соответствующие методы.

Давайте повторим. Полиморфизм, если по-простому – это когда объекты прикидываются не теми, кто они есть на самом деле, то есть мошенничают. Интерфейс – это не мошенничество, а честный договор. Объект объявляет: я никем не прикидываюсь, но я умею делать всё, что требует интерфейс, поэтому меня можно использовать по правилам интерфейса.

Интерфейс транспортного средства
Интерфейс транспортного средства

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

Мы сейчас задали интерфейс Vehicle (транспортное средство), который объявляет: чтобы меня применять, нужно реализовать методы

  • forward(speed) – движение вперед со скоростью speed
  • stop() – остановка
  • turn(direction) – поворот в сторону direction

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

А вот класс автомобиля, который применяет интерфейс Vehicle:

Ключевое слово здесь implements, то есть "применяет", или "реализует". Мы дали понять, что класс Car применяет интерфейс Vehicle, и значит все, кто умеют пользоваться интерфейсом Vehicle, могут пользоваться классом Car. Теперь транслятор с нас не слезет, пока не убедится, что мы поддерживаем данный интерфейс. Для этого нужно реализовать (написать) те методы, которые заявлены в Vehicle, то есть forward(), stop(), turn(). Автомобиль и подлодка, конечно, будут иметь свои реализации этих методов, но интерфейсу главное, чтобы они были. Именно поэтому в интерфейсе перечислены только их названия. Как конкретно они работают – это дело классов, их реализующих.

У класса Car есть ещё метод honk() (бибикнуть), но он уже не входит в интерфейс. Это собственный метод класса Car. Интерфейсу всё равно, сколько ещё методов находится в вашем классе. Главное, чтобы были все методы интерфейса.

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

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

Без интерфейса нам пришлось бы сделать класс Vehicle, реализовать в нём нужные методы и затем отнаследовать Car от Vehicle. Разница, можно сказать, микроскопическая, и поначалу не совсем понятно, чем всё-таки интерфейс отличается от наследования. Но обратите внимание: если реализовать в Vehicle методы для автомобиля, то что делать с подводной лодкой и звездолетом, ведь они тоже должны наследоваться от Vehicle?

Может, как вариант, реализовать в них свои собственные методы с помощью перезаписи или сделать Vehicle абстрактным классом? Можно и так, но интерфейс выглядит элегантнее.

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

И интерфейс, и абстрактный класс требуют реализовать некие методы. Абстрактный класс надо наследовать, а интерфейс применять, но это просто разный синтаксис, который в общем-то по барабану какой.

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

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

И тот и другой способ поначалу будут вызывать у вас вопросы, и возможно, что где-то действительно не будет никакой разницы, а где-то это будет важно. В любом случае, ваша практика сама покажет вам правильные пути. Применять интерфейсы исключительно ради того, чтобы их применять – не нужно. И конечно, узнайте, есть ли они в том языке, который вы изучаете. Нет интерфейсов – нет проблем :)

Наука
7 млн интересуются