Трейты в ООП PHP

Трейты



Трейты: php.net

Трейты




PHP не поддерживает множественное наследование. Частично эту проблему можно решить либо с помощью интерфейсов (можно реализовать несколько интерфейсов), либо цепочки классов (создав один класс, который будет наследовать другой класс, в свою очередь, второй класс будет наследовать третий и т. д.). Однако, данный момент, тоже имеет свои неудобства, свои ограничения.


Представим, что у нас появилось такое свойство (для гаджета), как $color. Если поместить его в свойста общего класса Product, то у нас получится: и в книге, и в ноутбуке есть характеристика - цвет.


Файл Product.php


-- файл Product.php --

<?php

namespace core;

abstract class Product
{
private $name;
protected $price;
private $discount = 0;

// Представим, что у нас появилось такое свойство (для гаджета), как color
private $color;

public function __construct($name, $price)
{
$this -> name = $name;
$this -> price = $price;
}
public function getProduct()
{
return "<hr><b>О товаре:</b><br>
Наименование: {$this -> name}<br>
Цена со скидкой: {$this -> getPrice()}<br>";
}

public function getName()
{
return $this -> name;
}

public function getPrice()
{
return $this -> price - ($this -> discount/100 * $this -> price);
}

public function getDiscount()
{
return $this -> discount;
}

public function setDiscount($discount)
{
$this -> discount = $discount;
}

abstract protected function addProduct($name, $price);
}
?>




Файл index.php:


-- файл index.php --

<?php

use app\{BookProduct,NotebookProduct};
use core\interfaces\{IGadget,I3D};

error_reporting(-1);

function debug($data)
{
echo '<pre>' . print_r($data, 1 ) . '</pre>';
}

function offerCase($product)
{
echo "<p>Предлагаем чехол для гаджета {$product -> getName()} </p>";
}

$book = new BookProduct('Три мушкетера', 20, 1000);

$notebook = new NotebookProduct('Dell', 1000, 'Intel');

offerCase($notebook);

debug($book);
debug($notebook);

?>

Получим:
Предлагаем чехол для гаджета Dell
app\BookProduct Object
(
[numPages] => 1000
[name:core\Product:private] => Три мушкетера
[price:protected] => 20
[discount:core\Product:private] => 5
[color:core\Product:private] => - и в книге, и в ноутбуке есть цвет
)
app\NotebookProduct Object
(
[cpu] => Intel
[name:core\Product:private] => Dell
[price:protected] => 1000
[discount:core\Product:private] => 0
[color:core\Product:private] => - и в книге, и в ноутбуке есть цвет
)




- для книги такая характеристика, как цвет - не нужна. Выносить такое свойство ($color) в общий класс нецелесообразно.

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


Получим:
Предлагаем чехол для гаджета Dell
app\BookProduct Object
(
[numPages] => 1000
[name:core\Product:private] => Три мушкетера
[price:protected] => 20
[discount:core\Product:private] => 5
)
app\NotebookProduct Object
(
[cpu] => Intel
[color:app\NotebookProduct:private] =>
- свойство цвет есть только для ноутбука
[name:core\Product:private] => Dell
[price:protected] => 1000
[discount:core\Product:private] => 0
)




Однако, у нас могут появиться какие-то гаджеты, которые не имеют свойства "цвет". Например, планшет - не имеет такую характеристику, как цвет.


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

Чтобы решить эту проблему используются трейты



Трейты




Трейт - это метод для повторного использования кода.

Трейт - по сути дела, тот же самый класс, но только класс, от которого нельзя создать объект.

В отличии от интерфейсов трейты могут иметь реализацию. Трейт можно включить в любой класс, от которого можно создать объект.

Трейт, в отличии от йнтерфейса, не меняет тип объекта, но меняет структуру класса.


Создадим отдельную папку trait в папке core, и в ней создадим трейт, который будет работать с цветом и назовем его - TColor.php. Из файла NotebookProduct.php убираем свойство $color и вместо него мы будем подключать трейт.


Файл TColor.php


-- файл TColor.php --

<?php

// добавляем пространство имен
namespace core\traits;

// трейт объявляется с помощью ключевого слова trait; и назовем его по имени файла TColor
trait TColor
{
// внутри идет реализация:
// нам необходимо наше свойство $color и нам понадобятся два метода: гетер и сетер

private $color;

// гетер
public function getColor()
{
return $this -> color;
}

// сетер
public function setColor($color)
{
$this -> color = $color;
return $this;
}
}
?>




В файле NotebookProduct.php подключаем с помощью ключевого слова use и имени трейта наш трейт - TColor, а также импортируем пространство имен: use core\traits\TColor;

Теперь, при распечатки объекта (debug($notebook)), видим, что у объекта $notebook появилось новое свойство - $color, а у объекта $book - ничего не изменилось


Файл NotebookProduct.php:


-- файл NotebookProduct.php --

<?php
namespace app;

use core\interfaces\IGadget;
use core\Product;
use core\traits\TColor; // импортируем постранство имен

// NotebookProduct, Product, IGadget ---- тип объекта
class NotebookProduct extends Product implements IGadget
{
// подключаем трейт с помощью ключевого слова use (имя трейта) -
// - фактически, мы сделали инклюд логики файла TColor.php:
use TColor;
public $cpu;

public function __construct($name, $price, $cpu)
{
parent :: __construct($name, $price);
$this -> cpu = $cpu;
}

public function getCase()
{
}

public function getProduct()
{
$out = parent :: getProduct();
$out .= "Процессор: {$this -> cpu}<br>";
return $out;
}

public function getCpu()
{
return $this -> cpu;
}

public function addProduct($name, $price, $cpu = '')
{
// --------------------------
var_dump($name);
var_dump($price);
var_dump($cpu);
}
}
?>




После обновления страницы получим:


Предлагаем чехол для гаджета Dell
app\BookProduct Object
(
[numPages] => 1000
[name:core\Product:private] => Три мушкетера
[price:protected] => 20
[discount:core\Product:private] => 5
)
app\NotebookProduct Object
(
[cpu] => Intel
[name:core\Product:private] => Dell
[price:protected] => 1000
[discount:core\Product:private] => 0
[color:app\NotebookProduct:private] =>
- свойство цвет есть только для ноутбука
)




Тип нашего объекта по прежнему будет -> NotebookProduct, Product, IGadget; но изменилась структура класса - используя use TColor мы фактически сделали инклюд, то есть фактически, мы взяли все содержимое кода трейта и вставили в класс NotebookProduct.


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


Содержание папки 14 ("Трейты"):


14-1






Наверх Наверх