Эта статья продолжает цикл материалов об объектно-ориентированном программировании. Если вы ещё не ознакомились с введением в ООП, следует это сделать.
В реальной жизни мы часто встречаем интерфейсы. Само слово "интерфейс" переводится как междумордие взаимодействие, или соприкосновение, то есть это точка соприкосновения. Например, интерфейс "Транспортное Средство" предназначен для взаимодействия (соприкосновения) водителя и транспортного средства. Что для этого нужно? Условно говоря, педаль "газ", педаль "тормоз" и руль. Стало быть, интерфейс "Транспортное Средство" объявляет всем – мне всё равно кто вы, но если вы хотите считаться транспортным средством, то вы должны иметь методы "вперед", "стоп" и "поворот".
И вот самые разные транспортные средства – такие как автомобиль, мотоцикл, экскаватор, подводная лодка, космический корабль – объявляют, что они применяют интерфейс "Транспортное средство", а значит любой, кто умеет пользоваться этим интерфейсом, может управлять ими через соответствующие методы.
Давайте повторим. Полиморфизм, если по-простому – это когда объекты прикидываются не теми, кто они есть на самом деле, то есть мошенничают. Интерфейс – это не мошенничество, а честный договор. Объект объявляет: я никем не прикидываюсь, но я умею делать всё, что требует интерфейс, поэтому меня можно использовать по правилам интерфейса.
Посмотрим, как оформляется интейфейс. Он похож на класс, то есть тоже является "чертежом" объекта и сам по себе инертен. Кроме того, он всегда только объявляет методы, но не реализует их:
Мы сейчас задали интерфейс 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 абстрактным классом? Можно и так, но интерфейс выглядит элегантнее.
Если вы читали тему про абстрактные классы, то можете заметить, что интерфейс это почти то же самое, что и абстрактный класс. Разницу между ними очень трудно объяснить, не потому что она сложна, а потому что она мизерна.
И интерфейс, и абстрактный класс требуют реализовать некие методы. Абстрактный класс надо наследовать, а интерфейс применять, но это просто разный синтаксис, который в общем-то по барабану какой.
Но всё же абстрактный класс обладает некоторыми расширенными возможностями. Во-первых, он может содержать в себе полноценные методы, которые можно вызывать статически, а интерфейс не может. Во-вторых, сам абстрактный класс может наследоваться от другого класса, а интерфейс не может.
Но с другой стороны, интерфейс не заставляет вас ничего наследовать. Он просто даёт соглашение, которое нужно поддерживать, и всё. Я бы осторожно сказал так: если у вас объекты связаны одним семейством, то вполне логично их наследовать, а если они совершенно разные, но должны иметь один интерфейс, то вот как раз и интерфейс.
И тот и другой способ поначалу будут вызывать у вас вопросы, и возможно, что где-то действительно не будет никакой разницы, а где-то это будет важно. В любом случае, ваша практика сама покажет вам правильные пути. Применять интерфейсы исключительно ради того, чтобы их применять – не нужно. И конечно, узнайте, есть ли они в том языке, который вы изучаете. Нет интерфейсов – нет проблем :)