@dasauser
Пишу на PHP

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

Прочитал кучу вопросов и статей, но повторю этот вопрос, который задавали до меня тысячи, а возможно десятки и сотни тысяч раз, снова:
Зачем нужны абстрактные классы и интерфейсы? И в php тоже.
Я понимаю так: абстрактный класс позволяет декларировать методы для дочерних классов, также не определять в классе наследнике методы родителя, и что по сути от обычного класса он отличается тем, что в названии некоторых методов и названии класса есть приписка "abstract" и что нельзя создать его экземпляр. Вопрос: зачем тогда он вообще нужен?
Как я понимаю, он (абстрактный класс) носит чисто декоративный характер, нужен для ограничения действий разработчика, удобства разработки и вообще это лишь "сахар".
Та же тема с интерфейсами. Зачем они нужны, если потом их методы и свойства будут переопределяться? А если не будут, то чем хуже наследование от обычного родительского класса, где так же можно определить свойства и методы, но опустить реализацию?
Есть ещё трейты, но с ними более-менее понятно. Чтобы не плодить классы родители и от них каждый раз не перенаследоваться, создавая при этом огромные цепочки наследований, используются трейты, в которых определяется метод, который есть в классе родителе, и потом трейт подключается к классу наследнику, переопределяет родительский метод и нет лишней возни.
Так и зачем нужны абстрактные классы и интерфейсы?
  • Вопрос задан
  • 12351 просмотр
Решения вопроса 4
hack504
@hack504
Да, носят чисто декларативный характер. Но в проектировании и при разработке в команде разработчиков очень сильно нужОн. Например, у нас есть некая сущность Тариф, от которой порождены конкретные реализации (Акционный, лимитный, безлимитный, специальный - не суть ) - их может что-то объединять/разделять. Поэтому, когда я пишу некий класс который как-то взаимодействует с Тарифом с определенным признаком, то я явно могу указать это создав специальный Интерфейс и запросить именно экземпляр класса конкретно этого интерфейса, а не родительского класса:
public function __construct(IDiscountByDay $Tariff){}

Далее уже сделать имплементацию этого интерфейса на всех наследниках класса Tariff где нужно, и не парится, что через какое-то время другой разработчик создаст новую реализацию тарифа и с твоего кода посыпятся баги
Ответ написан
Acuna
@Acuna
Заполнил свой профиль
Если совсем на пальцах для гарантированного понимания на наглядном примере: мы проектируем класс для работы с БД. Он должен работать с разными типами БД (MySQL, PostgreSQL, и т. д.). Каждый тип, с которым он может работать, мы реализовываем в виде отдельного класса (что в дальнейшем позволит просто добавлять к нашему проекту новые классы, не переписывая основной класс). Благодаря этому можно создавать другие классы на основе нашего абстрактного основного класса, не боясь, что необходимые для работы методы не будут определены (так как абстрактные методы обязаны быть переопределены при расширении функционала этого класса базовым абстрактным классом), что позволит предотвратить ситуации когда либо сам разраб забыл переопределить нужный для работы нашего расширения метод, либо другие программеры забыли/забили на это при создании своих расширений для этого класса, а тут компилятор скажет, мол, ты расширяешь свой класс базовым классом, это хорошо и правильно, но переопредели плиз эти абстрактные методы, ибо в них вообще вся суть расширения. Надеюсь хотя-бы это объяснение прольет свет. Штука крайне удобная, одна из самых удобных и нужных вещей в PHP и вообще во всех нормальных языках.
Ответ написан
Комментировать
@zhainar
Гуглю за вас
В принципе для того же, для чего и в реальном мире. Для взаимодействия двух объектов. Кому нужен этот юсб разъём? Я ща провода куда надо припояю и будет всё работать.
Ответ написан
С интерфейсами самый простой кейс:

1) Пишем какую-то функцию, которая принимает в качестве аргумент интерфейс.
2) Функция работает с методами интерфейса и получает какой-то результат.
3) Где-то есть отдельная логика, выбора конкретной реализации класса, который выполняет интерфейс.

Т.е. допустим, у нас есть онлайн-касса и есть 20 методов платежей. Сама онлайн-касса работает с интерфейсом проводки платежа, а как проводить платежи реализовано в конкретных классах платежных систем.

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

___

Абстрактный класс. ну допустим, мы далем абстрактный класс DTO, который выполняет методы to_string и normalize. Наследуюясь от этого абстрактного класса, мы во всех DTO получаем методы to_string и normalize. Плюс защищаемся от того, что кто-то решит нам запороать этот абстрактный класс, либо "решит" что-то сломать в своем DTO

___

Ну вообще, это все нужно, если ты пишешь хороший проект на Symfony командой. Если это какой-то бложит или небольшой новостник, тебе это все не нужно.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 8
search
@search
мама говорит что я особенный
Интерфейс - это контракт, который реализовывает класс, а пользователи класса знают как этот класс использовать. Это альтернатива множественному наследованию. Например у вас есть два типа коллекции: бинарное дерево и граф. Это довольно отличающиеся структуры данных. Но каждая из коллекций может реализовать интерфейс Iterator и в этом случае интерпретатор будет знать как перебрать коллекцию в цикле foreach.

Абстрактные классы в основном используются в том случае, если какую-то часть кода можно описать в родительском классе, но для того чтоб эта часть приобрела смысл, нужна конкретика: дополнить общую картину подробностями в виде методов или полей. Если вы присмотритесь к абстрактным классам в современных фреймворках, то увидите что сам по себе абстрактный класс не имеет смысла. Например, если создать объект такого класса оператором new, то этому объекту всё равно будет чего-то не хватать и именно это что-то и добавляют дочерние классы.
Ответ написан
php666
@php666
PHP-макака
так яснее?

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

-> абстрактный класс человек (имеет реализацию задекларированных в интерфейсе методов, имеет общую реализацию)
 -->  конкретный класс человек мужчина (частности реализации)
 -->  конкретный класс человек женщина (частности реализации)

-> абстрактный класс инопланетянин (имеет реализацию задекларированных в интерфейсе методов, имеет общую реализацию)
 -->  конкретный класс инопланетянин мужчина (частности реализации)
 -->  конкретный класс инопланетянин женщина (частности реализации)


Если ты был бы создателем, тебе потребовалась именно эта структура для создания гуманоидо-образного существа (две руки, две ноги, голова, прямоходящий, два глаза - это ИНТЕРФЕЙС - декларация, что все классы будут имплементировать это соглашение).

Человек не может наследоваться от инопланетянина, как и наоборот - это РАЗНЫЕ формы жизни. Одна построена на углеродной форме (человек), другая, скажем, на базе метаногенов. Но общее их - интерфейс ГУМАНОИД, созданный "по образу и подобию" (с).

Для этих двух разных типов мы создаем базовые АБСТРАКТНЫЕ классы (ибо ни человек, ни инопланетянин, с нашей точки зрения, как создателя, не может быть неопределенного пола). В абстрактных классах мы реализуем методы, которые задекларированы в нашем интерфейсе. Мы наполняем абстрактные классы свойствами и всеми методами, присущими обоим полам наших творений, ведь между мужчиной и женщиной очень много общего в строении.

Конкретные классы уже являются конечными полами двух разных типов форм жизни.

24301509.jpg
Ответ написан
Ninazu
@Ninazu
1. Абстрактный класс нельзя создать, только создать обьект от наследуемого класса
2. Абстрактный класс может содержать реализацию, в отличии от интерфейса
3. Абстрактный класс может содержать абстрактные методы, которые необходимо будет реализовать в дочерних классах
4. Можно имплементировать сразу несколько интерфейсов, а наследовать только от одного класса.
5. Трейты очень хорошо комбинируются с интерфейсами

Осознание зачем это нужно, прийдёт к вам в процессе использования)) Почитайте также про SOLID, и попробуйте реализовать эти принципы без абстракций и интерфейсов)

P.S. Домашнее задание
Есть три класса. Квадрат, Прямоугольник, Круг. И класс Ящик. Мы добавляем в ящик, произвольные наборы из фигур, и нужно посчитать площадь всех фигур в ящике.
Ответ написан
Tomio
@Tomio
backend developer (python, php)
Работая с ООП нужно четко вбить в себе в голову, что перед тобой не классы, а объекты. И разрабатывать архитектуру приложения нужно тоже с упором на это понятие.
Интерфейсы очень просто понять, для этого можно представить себе какой-то предмет с привычным для тебя названием "интерфейс". Это может быть:
  • пульт от телевизора с кнопками
  • сенсорная панель управления микроволновкой
  • приборная панель автомобиля
  • оболочка смартфона (Android, iOS, EMUI, MIUI и т.д.)
  • и пр.

Всё вышеописанное - это интерфейсы взаимодействия с внутренними механизмами приборов. Интерфейсы вносят некую абстракцию. Ты не знаешь, как работает прибор внутри. У тебя может быть один интерфейс, но реализация внутреннего функционала совершенно разная. То есть, например, у тебя есть тумблер на приборной панели автомобиля, который регулирует температуру кондиционера. Но ты не знаешь, что происходит внутри, когда ты его крутишь.
Перенесемся в программирование. Этот самый тумблер - это элемент интерфейса, он запускает публичный метод setTemperature. Это всё, что тебе нужно о нём знать.
То есть, резюмируя, интерфейсы - это декларированный абстрактный слой приложения с доступным ограниченным набором публичных методов взаимодействия с этим приложением. Твоё приложение может и будет содержать приватные методы, ибо если используешь ООП, инкапсуляция должна быть обязательно, чтобы скрыть в чёрный ящик ненужные для пользователя механизмы. Пользователю важно понимать, что покрутив тумблер, будет изменена температура - и всё. А уж как она будет изменена - это на суд программиста.

Абстрактный класс - это по сути тот же интерфейс, но только в нём можно заранее предопределить какие-то методы, и использовать их в наследуемом классе. Абстрактные методы же нужны для того, чтобы обязательно они были реализованы в классе наследнике, ибо без них класс наследник полноценно работать не будет.
Например, есть абстрактный класс, который работает с выборкой и перегоном данных из одной БД в другую. И все методы в нём уже реализованы, кроме одного абстрактного - getQuery, - метод, который возвращает строку с SQL запросом. Ведь запрос может быть разным и ты никогда не знаешь, какие в нем будут условия. Это какой-то абстрактный запрос, который неизвестен. И как раз под это действие очень удобно завести абстрактный метод.
Ответ написан
amark
@amark
rush less, feel more
Сейчас читаю эту старую статью, и вспомнил про ваш вопрос.
В статье пишут про принципы хорошей архитектуры ПО, и среди прочего объясняют, зачем нужны интерфейсы/абстрактные классы и т.п. Рекомендую.
Ответ написан
Комментировать
RabraBabr
@RabraBabr
Как это зачем? Какой сахар? Базовый абстрактный класс задает интерфейс. И потом несколько разных дочерних классов задают разную реализацию для разных типов объектов. Но благодаря базовому абстрактному классу вы сможете например эти разные объекты создавать одной фабрикой или использовать их в других паттернах и классы этих паттернов могут работать с этими объектами через интерфейс в принципе ничего не зная об этих объектах (об их реализации).

Вы сделав первый шаг, не поленитесь сделайте и второй. Почитайте о SOLID и GRASP про шаблоны проектирования. Когда вы выйдите на новый уровень абстрактного мышления. В голове произойдет (просветление) понимание ООП (а не вот эта вот примитивная мантра из трех слагаемых - инкапсуляция, наследование, полиморфизм). Большие коммерческие проекты все сплошь на абстракциях сделаны. Там все очень абстрактно.
Ответ написан
@vgbege
я бы тоже посоветовал начинать не с конца (зачем в языке высокого уровня абстрактные классы и интерфейсы), а с начала - как программирование развивалось от ассемблера/бейсика с кучей переменных и jmp/goto в структурную парадигму, а потом в ооп и зачем там понадобились абстрактные классы и интерфейсы. более того, я в php не спец и не знаю, как в этом слабо типизированном языке эти самые классы и интерфейсы реализованы :)

прямой ответ на вопрос "зачем" - чтобы реализовать композицию объектов. гуглить dependency injection, inheritance vs composition. вообще, вопрос зачем уже не стоит, сейчас это проверенные временем основы, которые нужно просто знать.

если на пальцах - в строго типизированных языках переменная типа IMyInterface может хранить экземпляры классов, реализующих IMyInterface. переменная типа MyAbstractClass может хранить экземпляры классов, порожденных от MyAbstractClass.
к примеру, от абстрактного класса Logger с методом log(s: string) мы можем породить реальные классы Console Logger, EmailLogger, DBLogger, SMSLogger и т.д., а потом подкидывать в девелоперском окружении консольный, а в боевом емейл и т.д.

надеюсь, я ответил на прямой вопрос и не слишком напряг отсылкой к базовым знаниям :D
Ответ написан
@terminator-light
Если у вас уже есть класс, который наследует другой класс, и какая-то библиотека требует передачи объекта, который относится к какому-то другому типу, и при этом вы не можете наследоваться от другого класса, т.к вы уже унаследовались от одного, ибо нет множественного наследования в вашем языке(например, возьмем Java):
class Coffee{
   
}

class Cappuccino extends Coffee{

}

//метод в библиотеке
public void serialize(Serializable s){

}

Либу спроектировали т.о., чтобы его метод принимал тип интерфейса, а не класса. И поэтому вам всего лишь нужно реализовать данный интерфейс:
class Cappuccino extends Coffee implements Serializable{

}

а абстрактный класс, думаю, это инструмент для подчеркивания намерений разработчика. Чтобы дать понять, что данный класс не предназначен для такого же использования как обычный класс, для инстанцирования.
Если интерфейс может показать какое-то поведение, например:
Serializable, Parcelable, Runnable, Callable, Comparable, Clonable
и т.д., то абстрактный класс показывает базовость, абстрактность, т.е. отношение is-a Возмьмем, абс. класс DataSource и его наследники RemoteDataSource, LocalDataSource и MemoryCacheDataSource, т.е. мы даем ясно понять, что просто так нельзя создать экземпляр DataSource.
Исходя из вышесказанного, можно выделить:
1. интерфейс привносит в язык множественность наследования типов
2. интерфейс и абстрактный класс служат декларативной целью
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы