Трейты в PHP
PHP поддерживает только одиночное наследование: дочерний класс не может наследовать от нескольких классов сразу, только от одного единственного родителя. Однако в большинстве случаев было бы полезно наследовать от нескольких классов. Например, было бы желательно наследовать методы от нескольких разных классов, чтобы предотвратить дублирование кода. Трейты используются для восполнения этого пробела, позволяя нам повторно использовать одни и те же свойства и методы в нескольких классах.
Что такое трейты?
Трейты (англ. trait) используются для объявления методов, которые можно использовать в нескольких классах. Трейты могут иметь методы и абстрактные методы, которые могут использоваться в нескольких классах. Методы трейтов могут иметь любой модификатор доступа (публичный, приватный или защищенный).
Синтаксис трейта такой же как и у класса, за исключением того, что имя трейта нужно объявлять с помощью ключевого слова trait
:
Синтаксис
<?php
trait TraitName {
// некоторый код...
}
?>
Экземпляр трейта, как и абстрактного класса, нельзя создать — трейты предназначены только для подключения к другим классам.
Само подключение осуществляется с помощью ключевого слова use
, после которого через пробел указывается имя подключаемого трейта. Данная команда пишется в начале класса:
Синтаксис
<?php
class MyClass {
use TraitName;
}
?>
Давайте посмотрим на пример в котором объявим один трейт и подключим к классу:
Пример
Попробуй сам »<?php
trait Reader {
public function add($var1,$var2){
return $var1+$var2;
}
}
class File {
use Reader; // подключаем трейт
public function calculate($var1,$var2) {
echo "Результат сложения: ".$this->add($var1,$var2) ."\n";
}
}
$o = new File();
$o->calculate(5,3);
?>
Результат выполнения кода:
В примере выше мы объявляем один трейт Reader
. Затем мы создаем класс File
. Класс использует трейт, и все методы, объявленные в трейте, будут доступны в классе. При этом обращаться мы к ним будем будто к методам самого класса.
Если другим классам необходимо использовать функцию add()
, просто используйте в этих классах трейт Reader
. Это уменьшает дублирование кода, потому что нет необходимости повторно объявлять один и тот же метод снова и снова.
По сути, трейт — это просто способ скопировать и вставить код во время выполнения.
Если вы знаете английский, то понимаете, что trait
означает именно то, что написано в названии — это бесклассовый пакет методов и свойств, которые вы присоединяете к существующим классам с помощью ключевого слова use
.
Использование нескольких трейтов
Для того, чтобы продемонстрировать преимущества трейтов, давайте сделаем еще один трейт writer
и еще один класс File2
.
Первый класс File
использует трейт Reader
, а второй класс File2
использует трейты Reader
и writer
(несколько трейтов разделяются запятыми):
Пример
Попробуй сам »<?php
trait Reader{
public function add($var1,$var2){
return $var1+$var2;
}
}
trait writer {
public function multiplication($var1,$var2){
return $var1*$var2;
}
}
class File {
use Reader;
public function calculate($var1,$var2){
echo "Результат сложения: ".$this->add($var1,$var2) ."\n";
}
}
class File2 {
use Reader;
use writer; // альтернатива записи: use Reader, writer;
public function calculate($var1,$var2){
echo "Результат сложения: ".$this->add($var1,$var2) ."\n";
echo "Результат умножения: ".$this->multiplication($var1,$var2);
}
}
$o = new File();
$o->calculate(5,3);
$o2 = new File2();
$o2->calculate(6,4);
?>
Результат выполнения кода:
Результат сложения: 10
Результат умножения: 24
Чем трейты отличаются от интерфейсов?
Трейты очень похожи на интерфейсы. И трейты, и интерфейсы обычно просты, лаконичны и мало используются без реально реализованного класса. Однако разница между ними есть.
Интерфейс — это контракт, в котором говорится, что "этот объект может делать это", тогда как трейт дает объекту возможность делать это.
Другими словами, если код ООП касается планирования и проектирования, то интерфейс — это план, а объект — полностью построенный дом. Между тем, трейты — это просто способ помочь построить дом, спроектированный по плану (интерфейсу).
Интерфейсы — это спецификации, которые можно проверить используя оператор instanceof
(является ли текущий объект экземпляром указанного класса).
Оператор instanceof
не будет работать с трейтами (т.к. трейт не является реальным объектом), поэтому вы не можете использовать instanceof
, чтобы увидеть, есть ли у класса определенный трейт (или чтобы увидеть, разделяют ли два не связанных между собой класса трейт).
Вы должны использовать трейты только тогда, когда несколько классов имеют одну и ту же функциональность (вероятно, продиктованную одним и тем же интерфейсом). Нет смысла использовать трейт для обеспечения функциональности для одного класса: это только запутывает то, что делает класс.
Например:
Пример
Попробуй сам »<?php
interface Person {
public function greet();
public function eat($food);
}
trait EatingTrait {
public function eat($food) {
$this->putInMouth($food);
}
private function putInMouth($food) {
// Переваривайте вкусную еду
}
}
class NicePerson implements Person {
use EatingTrait;
public function greet() {
echo 'Добрый день, сэр!';
}
}
class MeanPerson implements Person {
use EatingTrait;
public function greet() {
echo 'Твоя дочь на ужине была хомяком!';
}
}
$niceperson = new NicePerson;
if($niceperson instanceOf Person) {
$niceperson->greet();
}
echo "<br>";
$meanperson = new MeanPerson;
if($meanperson instanceOf Person) {
$meanperson->greet();
}
?>
Результат выполнения кода:
Твоя дочь на ужине была хомяком!
Основное отличие состоит в том, что с интерфейсами вы должны определить фактическую реализацию каждого метода в каждом классе, реализующем указанный интерфейс, поэтому вы можете иметь множество классов, реализующих один и тот же интерфейс, но с различным поведением. В то время как трейты — это просто фрагменты кода, введенные в класс. Еще одно важное отличие состоит в том, что методы трейтов могут быть только методами класса или статическими методами, в отличие от методов интерфейса, которые также могут (и обычно являются) методами экземпляра.
Переопределение унаследованных методов
Как описано в Руководстве: Унаследованный метод от базового класса переопределяется методом, вставленным с помощью трейта. Порядок приоритета таков — методы текущего класса переопределяют методы трейта, которые в свою очередь переопределяют унаследованные методы.
Итак, рассмотрим следующий сценарий:
Пример
Попробуй сам »<?php
class BaseClass {
function SomeMethod() { echo "BaseClass"; }
}
interface IBase {
function SomeMethod();
}
trait myTrait {
function SomeMethod() { echo "myTrait"; }
}
class MyClass extends BaseClass implements IBase {
use myTrait;
function SomeMethod() { echo "MyClass"; }
}
$myclass = new MyClass;
$myclass->SomeMethod();
?>
Результат выполнения кода:
При создании экземпляра MyClass, описанного выше, происходит следующее:
- Интерфейс
IBase
включают абстрактный метод без параметров под названиемSomeMethod()
, который должен быть реализован в наследуемом классе. - Базовый класс
BaseClass
предоставляет реализацию этого методаSomeMethod()
. - У трейта
myTrait
также есть вызываемая функция без параметровSomeMethod()
, которая имеет приоритет надBaseClass
-версиями. - Класс
MyClass
предоставляет свою собственную версию методаSomeMethod()
, которая имеет приоритет надтрейт
-версиями.
Заключение
- Интерфейс, по умолчанию, не может предоставить реализацию тела метода, в то время как трейт может.
- Интерфейс является полиморфным, один или несколько классов используют один и тот же интерфейс — в то время как у трейта нет такой полиморфной конструкции, потому что трейт по сути является просто кодом, который копируется для удобства программиста в каждый класс, который его использует.
- В одном классе можно использовать несколько интерфейсов, а также несколько трейтов.