Для чего нужны абстрактные классы php
Абстрактный класс представляет частичную реализацию для классов-наследников.
Абстрактный класс определяется с помощью модификатора abstract , который ставится перед именем класса:
abstract class Messenger
Одной из ключевых особенностей абстрактных классов является то, что мы не можем напрямую создать объекты абстрактного класса с помощью вызова его конструктора:
abstract class Messenger < >$telegram = new Messenger(); // эта строка не будет работать
Абстрактные классы, как и обычные классы, могут определять переменные и константы, методы и конструкторы.
Но другой особенностью абстрактных классов является то, что они могут содержать абстрактные методы. Это методы, которые не имеют реализации. Реализацию для них предоставляют классы-наследники. Например:
abstract class Messenger < abstract function send($message); // абстрактный метод >
При определении абстрактного метода перед словом function ставится модификатор abstract . А после списка параметров метода — точка с запятой.
Абстрактные методы могут размещаться только в абстрактных классах. Обычный неабстрактный класс не может иметь абстрактных методов.
Если неабстрактный класс наследуется от абстрактного класса, то он обязан реализовать все его абстрактные методы.
Для наследования классом абстрактного класса, как и в общем случае при наследовании, применяется ключевое слово extends . Например:
name = $name; > abstract function send($message); function close() < echo "Выход из мессенджера. "; >> class EmailMessenger extends Messenger < function send($message) < echo "$this->name отправляет сообщение: $message
"; > > $outlook = new EmailMessenger("Outlook"); $outlook->send("Hello PHP 8"); $outlook -> close(); ?> ?>
В данном случае класс EmailMessenger наследуется от абстрактного класса Messenger.
Абстрактный класс определяет абстрактный метод send() , поэтому класс-наследник EmailMessenger должен предоставить реализацию для этого метода.
Так, в данном случае мы получим следующий вывод:
Outlook отправляет сообщение: Hello PHP 8 Выход из мессенджера.
Можно заметить, что абстрактные классы похожи на интерфейсы — и те, и другие могут определять методы без реализации, которые реализуются в других классах. Однако, абстрактные классы, как и обычные классы, могут иметь переменные, неабстрактные методы, конструкторы с реализацией, а интерфейсы нет. Кроме того, в PHP один класс может наследоваться только от одного класса, тогда как один класс может применять сразу несколько интерфейсов.
PHP: Интерфейсы vs Абстрактные классы
Недавно я опубликовал статью об улучшении PHP кода с помощью интерфейсов. Она охватывает основы того что такое интерфейс, что он может делать. И как вы можете использовать его сделав свой PHP код более расширяемым и поддерживаемым. Один из вопросов, заданных в комментариях к статье был от разработчиков, которые хотели знать «когда я должен использовать интерфейс вместо абстрактного класса?». Я подумал и решил написать статью, что бы объяснить различия между абстрактными классами и интерфейсами в PHP и дать краткий обзор того, когда вы должны использовать каждый из них.
Что такое Интерфейсы
В общих чертах, Интерфейс должен описывать, как будет построен класс реализующий его, это похоже на план, описывающий публичные методы и константы, которые должны быть реализованы.
Интерфейсы должны:
- Использоваться для определения публичных методов класса.
- Использоваться для определения констант класса.
Интерфейсы не должны:
- Использоваться сами по себе.
- Использоваться для определения приватных или защищённых методов класса.
- Использоваться для определения свойств класса.
Интерфейсы используются для определения публичных методов, которые должен включать класс. Важно помнить, что интерфейс всегда предназначен для реализации классом, поэтому в нём вы определяете только сигнатуру метода, например:
interface HomeInterface
const MATERIAL = 'Brick';
public function openDoor(): void;
public function getRooms(): array;
public function hasGarden(): bool;
>
а не как в следующем примере:
interface HomeInterface
public string $material = 'Brick';
public function openDoor(): void
// Открываем дверь.
>
public function getRooms(): array
// Даём информацию о комнатах.
>
public function hasGarden(): bool
// Задаём есть ли у дома сад.
>
>
Согласно php.net, интерфейсы служат нескольким целям:
- Позволить разработчикам создавать объекты разных классов, которые могут использоваться взаимозаменяемо, поскольку они реализуют один и тот же интерфейс или интерфейсы. Типичный пример — несколько служб доступа к базе данных, несколько платёжных шлюзов или разные стратегии кэширования. Различные реализации могут быть заменены, не требуя каких-либо изменений в коде, который их использует.
- Позволить функции или методу принимать и оперировать параметром, который соответствует интерфейсу, не заботясь о том, что ещё может делать объект или как он реализован. Эти интерфейсы часто называют Iterable , Cacheable , Renderable и т.д., чтобы описать поведение.
Используя наш выше описанный интерфейс и придерживаясь аналогии с домом, мы могли бы создавать различные классы реализующие HomeInterface такие, как House , Flat или Caravan . Используя интерфейс, мы можем быть уверены, что наш класс содержит три необходимых метода и все используют правильную сигнатуру метода. Например, у нас может быть класс House , который выглядит так:
class House implements HomeInterface
public function openDoor(): void
// Открываем дверь здесь.
>
public function getRooms(): array
// Даём информацию о комнатах.
>
public function hasGarden(): bool
// Задаём есть ли у дома сад.
>
>
Что такое Абстрактные классы
Абстрактные классы PHP очень похожи на интерфейсы PHP; они не предназначены быть классами сами по себе и представляют базовые методы без реализации.
Возьмём пример с домами, приведённый выше, если интерфейс является вашим планом, то абстрактный класс — это ваша модель выставочного зала. Он работает, и это отличный пример дома, но вам всё равно нужно его обставить и украсить, чтобы сделать его своим.
Абстрактный класс может:
- Использоваться для определения сигнатур методов класса с использованием abstract методов (аналогично интерфейсам).
- Использоваться для определения методов.
- Использоваться для определения констант класса.
- Использоваться для определения свойств класса.
- Расширятся дочерним классом.
Абстрактный класс не может:
- Использоваться самостоятельно
Что бы понять, что это означает, рассмотрим пример абстрактного класса:
abstract class House
const MATERIAL = 'Brick';
abstract public function openDoor(): void;
public function getRooms(): array
return [
'Bedroom',
'Bathroom',
'Living Room',
'Kitchen',
];
>
public function hasGarden(): bool
return true;
>
>
Наш класс House объявлен abstract — это означает, что мы не можем использовать его напрямую. Для использования нужно его унаследовать. Например, давайте создадим класс MyHouse , который расширяет абстрактный класс House :
class MyHouse extends House
public function openDoor(): void
// Открываем дверь.
>
public function getRooms(): array
return [
'Bedroom One',
'Bedroom Two',
'Bathroom',
'Living Room',
'Kitchen',
];
>
>
// Это не будет работать:
$house = new House();
// Это будет работать:
$house = new MyHouse();
Вы могли заметить, что в классе House мы объявили абстрактный публичный метод openDoor() . Это позволяет нам определить сигнатуру метода, которую дочерний класс должен включать, аналогично тому, как мы делали бы с интерфейсом. Это действительно удобно, если вы хотите поделиться функциональностью с дочерними классами, но при этом обеспечить возможность собственной реализации некоторых методов.
В этом случае дочерний класс мог бы переопределить методы getRooms() и hasGarden() , но не должен был бы их включать. Что продемонстрировать это, мы переопределили метод getRooms() показав, как мы можем изменить его поведение в дочернем классе.
Как решить, что использовать
Это зависит от вашей цели. Сохранив аналогию с домом, если вы создаёте чертежи, которые в дальнейшем можно использовать для проектирования домов разных типов, вам нужен интерфейс.
Если вы построили дом и вам нужно создать копии с улучшениями, то вам нужен абстрактный класс.
Приведу несколько примеров:
Когда использовать Интерфейс
Чтобы помочь понять, когда нужно использовать интерфейс, давайте рассмотрим пример. Допустим, у нас ест класс ConstructionCompany включающий метод buildHome() , который выглядит следующим образом:
class ConstructionCompany
public function buildHome($home)
// Строим дом.
return $home;
>
>
Теперь предположим, что у нас есть 3 разных класса, которые мо хотим создать и передать методу buildHome() :
- class MyHouse implements HomeInterface extends House
- class MyCaravan implements HomeInterface
- class MyFlat implements HomeInterface
Как мы видим, класс MyHouse расширяет абстрактный класс House ; и это имеет смысл к концептуальной точки зрения, потому что дом есть дом. Однако класс MyCaravan или MyFlat не имеет смысла расширять от абстрактного класса, потому что ни один из них не является домом.
Итак, поскольку наша строительная компания может строить дома, фургоны и квартиры, это правило исключает указание параметра $home в методе buildHome() как экземпляра House .
Однако это было бы идеальным местом для указания типа нашему методу, что бы разрешить принимать только классы реализующие HomeInterface . В качестве примера мы могли бы обновить метод следующим образом:
class ConstructionCompany
public function buildHome(HomeInterface $home)
// Строим дом.
return $home;
>
>
В результате мы можем быть уверенными, передавая значение дом, фургон или квартира, что наш класс ConstructionCompany получит необходимую информацию. Переданный объект home всегда будет содержать необходимые нам методы.
Вы могли подумать: Почему бы нам просто не создать абстрактный класс Home вместо интерфейса? . Однако важно помнить, что PHP поддерживает только одиночное наследование и что класс не может расширять более одного родителя. Таким образом, это будет довольно сложно, если вы захотите расширить один из своих классов в будущем.
Когда использовать Абстрактный класс
Возьмём сценарий аналогичный приведённому выше. Давайте представим, что у нас есть HouseConstructionCompany , похожая на нашу ConstructionCompany . Но в этом примере предположим, что компания HouseConstructionCompany строит только дома и ничего больше.
Поскольку мы знаем, что нам нужно уметь строить только дома, мы можем указать, что бы наш метод принимал только те классы, которые являются расширением абстрактного класса Home . Это может быть действительно полезно, так как мы всегда будем уверены, что передаём типы только тех домов которые строительная компания строит. Например:
class HouseConstructionCompany
public function buildHouse(House $house)
// Build the house here.
return $house;
>
>
Вывод
Надеюсь эта статья даст представление о различиях между интерфейсами и абстрактными классами в PHP. А так же различных сценариев того, когда следует их использовать.
PHP: Разница между Трейтом, Интерфейсом и Абстрактным классом
Эти три структуры могут сбить с толку новичков в PHP и опытных разработчиков, поэтому давайте рассмотрим, что делает каждая из них и когда их лучше использовать
www.dev-notes.ru
PHP: Используем Интерфейсы для улучшения кода
Важно убедится, что ваш код легко читается, поддерживается, расширяется и тестируется. Один из способов улучшить эти факторы — использовать интерфейсы.
Зачем нужны интерфейсы и абстрактные классы
До недавнего времени мне было трудно понять, зачем же нужны абстрактные классы и интерфейсы и в чём между ними разница. Однако теперь на реальных примерах мне все же удалось это понять, и я готов поделиться своим видением с читателями. В качестве примера мы рассмотрим TypeScript.
Интерфейсы
Давайте представим, что у нас имеется автомастерская WorkShop. Она может ремонтировать 3 вида автомобилей BMW, Mercedes и Porsche. В этом случае наш код будет выглядеть следующим образом:
void необходимо указывать в случаях, когда наш метод ничего не возвращает
Мы создали 3 класса для каждого автомобиля. У каждого класса имеются методы fixEngine и ChangeWheels. Каждый метод в каждом классе реализован по-разному для разных марок автомобилей. Основной класс WorkShop на вход принимает vehicle, который может быть 3 разных типов, поэтому мы указываем их через знак |.
В данном случае мы конкретно указываем тип автомобилей в конструкторе, которые принимает Workshop. Если бы мы этого не сделали, тогда бы мы не были уверены в том, что объект в переменной vehicle имеет методы fixEngine и changeWheels, что привело бы к ошибкам.
Но теперь возникает проблема — что будет, если наша мастерская через год начнет обслуживать дополнительно 10 типов автомобилей? В этом случае наш конструктор будет выглядеть как-то так:
constructor(public vehicle: BMW | Mercedes | Porsche | Kia | Lada | Ferrari | Bugatti | Mazda | Toyota) <>
Согласитесь, вариант не очень привлекательный. Однако решение есть! Мы можем использовать интерфейс 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.
Для чего нужны абстрактные классы и интерфейсы PHP?
Пример из жизни если можно =) Вроде писать классы можно и без них Где их можно применить? Вот нашел развернутый ответ ссылка на ответ Объясните на примере по ссылке почему нельзя вместо абстрактного класса использовать обычный класс, например не реализовывая какие то функции ы нем а реализовывая в потомках Ведь по сути обычный класс ничем не отличается от абстрактного, зачем тогда он нужен (абстрактный)?
Отслеживать
16.6k 8 8 золотых знаков 26 26 серебряных знаков 56 56 бронзовых знаков
задан 12 янв 2013 в 5:28
2,274 5 5 золотых знаков 30 30 серебряных знаков 53 53 бронзовых знака
Можно вообще и без классов писать, любых. Вопрос только в удобстве и эффективности проектирования, разработки, сопровождения и расширения кода. Ну и тык мышой.
12 янв 2013 в 5:47
Самое актуальное в этом вопросе — просьба о примерах из жизни IMHO речь именно о PHP. — А по сути вопрос о том, можно ли (точнее, насколько разумно) делать большие, эволюционирующие (а для чего еще нужны абстрактные классы?) системы на PHP.
12 янв 2013 в 15:26
6 ответов 6
Сортировка: Сброс на вариант по умолчанию
Можно, конечно, и без абстрактных классов. Но с ними все же удобнее. Например в следующем случае: вам надо реализовать иерархию классов, где есть один базовый класс и несколько производных. При этом нужно реализовать некий метод, который должен быть у всех классов, но во всех он должен быть реализован по-своему. Реализовать этот метод в базовом не абстрактном классе вы не можете — в таком случае он будет общим для всех производных. Можно, конечно, сделать его виртуальным в базовом классе и переопределять в производных, но тогда напрашивается вопрос: зачем этот метод реализовывать в базовом классе, если нужны только его реализации, зависящие от конкретного производного класса. Это иллюстрируется хрестоматийным примером про геометрические фигуры, когда у вас есть базовый класс Figure и производные от него Circle, Square, каждый из которых должен иметь метод для отрисовки (скажем, Draw()). Тут можно увидеть, что во-первых, Draw() должен быть разным для каждого из производных классов, а во-вторых, его не очень-то и нужно реализовывать в базовом, так как непонятно, как должна выглядеть «просто фигура». При этом у класса Figure может быть какой-то набор базовых методов, реализация которых может быть общей для всех производных. То есть вполне разумно сделать базовый класс именно абстрактным (а не интерфейсом или обычным, не абстрактным классом)
Отслеживать
ответ дан 12 янв 2013 в 7:02
DreamChild DreamChild
36.2k 3 3 золотых знака 45 45 серебряных знаков 85 85 бронзовых знаков
А мне вот интересно. PHP ведь динамический язык. Зачем в динамических языках вообще интерфейсы? $foo.getBar(); Как разница, есть ли у foo метод getBar , всё равно ведь в рантайме только узнаем.
17 мая 2014 в 20:40
@Veikedo, например Вы пишите интерфейс CRUD, который предполагает наличие у объекта свойств create, read, update, delete. Методы Вашего класса проверяют, что им передан объект реализующий этот интерфейс и значит не нужно проверять наличие этих методов. Другой программист, решивший воспользоваться Вашим классом делает реализацию интерфейса и передает объекты реализующие этот интерфейс в Ваш класс. Не знаю, как в IDE для php, но в Eclipce очень удобно написав: class myProvider implements CRUD < увидеть автоматически дополненые методы, которые надо реализовать из интерфейса.
17 мая 2014 в 21:04
@Veikedo, банально проверить плагин на работоспособность до его подключения, например. Разом проверить переданный логгер на наличие дополнительного функционала. Вообще, абстрактные классы и интерфейсы — это не про «методы, которые необходимо реализовать». Это базовые реализации с «обещаниями» и контракты.
