9. Трейти
Трейти — це структури, аналогічні класам. Проте їх не можна інстанціювати. Вони призначені для включення в класи. Включення трейту в клас дає той самий ефект, що й копіювання коду трейту в клас. У трейт поміщають код, який можна повторно використовувати в кількох класах.
9.1. Дерево скриптів

9.2. Включення риси до класу
Скрипт [traits-01.php] демонструє базове використання риси в класі:
<?php
class Class04 {
// атрибут
private $name;
// конструктор
public function __construct(string $name) {
$this->name = $name;
}
// методи getter та setter
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
trait Trait04 {
// атрибут
private $name;
// методи getter та setter
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
class Class05 {
// включення риси
use Trait04;
// конструктор
public function __construct(string $name) {
$this->name = $name;
}
}
// тест --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// виведення двох класів
print_r($class04);
print_r($class05);
Коментарі до коду
- рядки 3–21: визначення класу [Class04] з атрибутом, методами get/set та конструктором;
- рядки 23–36: беремо код з [Class04] без його конструктора та переносимо його в трайт [Trait04] без змін. Конструктор не переносимо, оскільки трайт не підлягає інстанціюванню;
- рядок 23: саме ключове слово [trait] робить [Trait04] рисою, а не класом;
- рядки 38–45: визначається клас [Class05], який переймає код риси [Trait04] (рядок 40) і додає до нього конструктор (рядки 43–45), ідентичний конструктору класу [Class04], щоб зробити клас інстанційованим;
- рядок 40: саме ключове слово [use] дозволяє включити рис у клас;
- рядки 50–56: тести показують, що класи [Class04] та [Class05] працюють однаково;
Результати
Результати у рядках 3–10 показують, що класи [Class04] та [Class05] мають однаковий вміст;
Висновок
Використання інструкції [use Trait] у класі еквівалентно включенню коду [Trait] до класу.
9.3. Використання одного й того самого «тракту» в різних класах
Однією з головних переваг «трасу» є можливість повторного використання одного й того самого коду (атрибутів та методів) у різних класах. Однак ми побачимо, що того самого результату можна досягти, використовуючи прості класи.
Спільне використання trait між класами проілюстровано наступним скриптом [trait-02.php]:
<?php
trait Trait01 {
// атрибут
private $id = 0;
// метод
public function doSomething() {
print "Trait01::doSomething… ($this->id)\n";
}
}
class Class02 {
// включення Trait01
use Trait01 {
// метод [Trait01::doSomething] доступний
// у класі під назвою [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
// метод, властивий класу
public function doSomething(): void {
// атрибут id
$this->id += 10;
// використання методу Trait01
$this->doSomethingInTrait();
// локальне відображення
print "Class02->doSomething\n";
}
}
class Class03 {
// включення методу Trait01
use Trait01;
// локальний метод класу
public function doSomethingElse(): void {
// атрибут id
$this->id += 10;
// використання методу Trait01
$this->doSomething();
// локальне відображення
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
$class02 = new Class02();
$class03 = new Class03();
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
Коментарі
- рядки 3–10: елемент, що визначає атрибут (рядок 5) та метод (рядки 8–10).
- Трейт [Trait01] вводиться у два класи: [Class02] (рядки 14–32) та [Class03] (рядки 34–46).
- рядки 16–20: введення [Trait01] у [Class02];
- рядок 19 призначений для вирішення конфлікту: класи [Trait01] та [Class02] мають метод з назвою [doSomething]. Слід передбачити два випадки:
- метод [Class02::doSomething] викликається ззовні класу. У цьому випадку метод [Class02::doSomething] має пріоритет над методом [Trait01::doSomething], і саме він викликається;
- метод [Class02::doSomething] викликається зсередини класу. У цьому випадку виникає конфлікт: інтерпретатор PHP не знає, який метод викликати;
У рядку 19 метод [Trait01::doSomething] перейменовано на [doSomethingInTrait]. Таким чином, усередині [Class02] будуть використовуватися такі позначення:
- [doSomethingInTrait] для виклику методу [Trait01::doSomething];
- [doSomething] для виклику методу [Class02::doSomething];
- рядки 25, 27: клас [Class02] використовує атрибут і метод класу [Trait01] так, ніби вони є його власними;
- рядки 34–48: клас [Class03] ідентичний класу [Class02]. Включення класу [Trait01] тут простіше, оскільки немає конфлікту між методами класів [Trait01] та [Class03];
Результати
Слід зауважити, що ознака [Trait01] не є спільною для класів [Class02] та [Class03]. Таким чином,атрибут [Trait01::i], завдяки включенню [Trait01] до класів [Class02] та [Class03], перетворюється на два різних атрибути: [Class02::i] та [Class03::i]. Про це свідчать рядки 2 та 4 результатів. Якби атрибут [Trait01::i] був спільним для класів [Class02] та [Class03], у рядку 4 було б 20 замість 10.
Скрипт [trait-03.php] показує, що можна отримати той самий результат, використовуючи клас замість ознаки:
<?php
// клас, що замінює рис
class Class01 {
// атрибут
private $id = 0;
// сеттер
public function setId(int $id) {
$this->id = $id;
}
// геттер
public function getId(): int {
return $this->id;
}
// метод
public function doSomething(): void {
print "Class01::doSomething… ($this->id)\n";
}
}
class Class02 {
// включення Class01
private $class01;
// сетер
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// метод, властивий класу
public function doSomething(): void {
// зміна атрибута Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// використання методу класу Class01
$this->class01->doSomething();
// локальне відображення
print "Class02->doSomething\n";
}
}
class Class03 {
// включення Class01
private $class01;
// сеттер
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// локальний метод класу
public function doSomethingElse(): void {
// зміна атрибута класу Class01
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// використання методу класу Class01
$this->class01->doSomething();
// локальне відображення
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
// два об’єкти
$class02 = new Class02();
$class03 = new Class03();
// звернуться до двох різних екземплярів [Class01]
$class02->setClass01(new Class01());
$class03->setClass01(new Class01());
// перевірка
$class02->doSomething();
$class03->doSomethingElse();
}
// test02 ----------------
function test02(): void {
// спільний екземпляр [Class01]
$class01 = new Class01();
// два об’єкти
$class02 = new Class02();
$class03 = new Class03();
// отримають доступ до одного й того ж екземпляра [Class01]
$class02->setClass01($class01);
$class03->setClass01($class01);
// перевірка
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
// тест02
print "test02-----------------\n";
test02();
Коментарі
- рядки 4–23: клас [Class01] замінює код [Trait01]. Код [Trait01] був включений до коду класів [Class02] та [Class03]. У даному випадку це не так. У класи [Class02] та [Class03] буде вставлено посилання на код класу [Class01]. Це означає, що атрибути класу [Class01] будуть зовнішніми щодо коду класів [Class02] та [Class03]. Оскільки в даному випадку атрибут [id] є приватним (рядок 6), необхідно передбачити геттер (рядки 14–16) та сеттер (рядки 9–11). Це перша відмінність від риси: потрібно створити код для доступу до приватних атрибутів класу. Можна було б змінити видимість атрибута [id] на [public], але робити це ніколи не рекомендується. Використання сетера для встановлення значення атрибута дозволяє перевірити його правильність;
- рядок 27: включення в код [Class02] посилання на клас [Class01]. Оскільки цей атрибут є приватним, нам потрібно створити сеттер (рядки 30–32) для його ініціалізації;
- рядки 37–41: для будь-якого використання коду [Class01] нам потрібно використовувати атрибут [$this→class01];
- рядки 48–69: клас [Class03] є клоном класу [Classe02], за винятком того, що його метод має іншу назву;
- рядки 72–82: перший тест. Він полягає у введенні в класи [Class02] та [Class03] двох різних екземплярів класу [Class01] (рядки 77 та 78);
- рядки 85–97: другий тест вводить у класи [Class02] та [Class03] один і той самий екземпляр класу [Class01] (рядки 97, 92, 93);
- рядки 100–101: виконання тесту [test01];
- рядки 103–104: виконання тесту [test02];
Результати
Коментарі до результатів
- рядки 2–5: отримано ті самі результати, що й у випадку з об’єктом [Trait01]. З цього можна зробити висновок, що використання трейсу тут не є обов’язковим, але воно дозволяє скоротити код, оскільки не потрібно використовувати методи для доступу до атрибутів трейсу: вони є невід’ємною частиною коду, в який було вбудовано трейс;
- рядки 7–10: оскільки ми ввели одне й те саме посилання на [Class01] у класи [Class02] та [Class03], атрибут [Class01::id] став спільним для обох класів. Саме тому у рядку 9 результатів відображається 20 замість 10 із використанням трейсу. З цього можна зробити висновок, що якщо атрибути трейсу мають бути спільними для класів, то трейс не можна використовувати, і в такому разі слід застосовувати клас;
9.4. Об’єднання методів у трет
У попередньому прикладі трайт містив атрибути та методи. Тут ми розглянемо випадок, коли він містить лише методи. У цьому випадку трайт нагадує факторизацію методів, які потім можна використовувати в різних класах. Оскільки трайт тут не має атрибутів, ми розглянемо випадок, коли методи, які він об’єднує, працюють виключно з параметрами, що їм передаються. Насправді це не є обов’язковим: трейт може оперувати атрибутом [$this→attribut1], не маючи цього атрибута. У такому разі класи, які використовують цей трейт, повинні надати атрибут [$this→attribut1].
У випадку, коли trait має лише методи, які працюють виключно з параметрами, що їм передаються, ми покажемо, що trait можна замінити класом, який має ті самі методи, що й trait, але оголошені статичними.
Використання трейту проілюстровано скриптом [trait-04.php], який використовує трейт із попереднього прикладу, видаливши з нього всі атрибути:
<?php
trait Trait01 {
// метод для спільного використання
public function doSomething() {
print "Trait01::doSomething ….\n";
}
}
class Class02 {
// включення Trait01
use Trait01 {
// метод [Trait01::doSomething] доступний
// у класі під назвою [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
public function doSomething(): void {
// виклик методу Trait01
$this->doSomethingInTrait();
// локальне відображення
print "Class02->doSomething\n";
}
}
class Class03 {
// включення методу Trait01
use Trait01;
// локальний метод класу
public function doSomethingElse(): void {
// виклик методу Trait01
$this->doSomething();
// локальне відображення
print "Class03->doSomethingElse\n";
}
}
// тест ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Коментарі
- рядки 3–10: трайт [Trait01] більше не має атрибутів;
- рядки 14–18: включення [Trait01] у [Class02]. У рядку 22 використовується метод [Trait01];
- рядок 31: включення [Trait01] у [Class03]. Метод [Trait01] використовується у рядку 36;
Результати
У цьому випадку об’єкт [Trait01] можна легко замінити на клас. Це продемонстровано у наступному скрипті [trait-05.php]:
<?php
abstract class Class01 {
// статичний метод для спільного використання
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// виклик методу класу Class01
Class01::doSomething();
// локальне відображення
print "Class02->doSomething\n";
}
}
class Class03 {
// локальний метод класу
public function doSomethingElse(): void {
// виклик методу класу Class01
Class01::doSomething();
// локальне відображення
print "Class03->doSomethingElse\n";
}
}
// тест ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
Коментарі
- рядки 3–10: риса [Trait01] замінюється абстрактним класом [Class01], усі методи якого оголошені статичними. Клас оголошено абстрактним виключно для того, щоб запобігти його інстанціюванню. Ми хотіли б також написати [final], щоб запобігти його похідним класам, але PHP 7 не допускає префікс [final abstract] для класу. Можна вибрати або одне, або інше, але не обидва;
- рядок 16: замість [$this→doSomethingInTrait] тепер пишемо [Class01::doSomething], тобто викликаємо статичний метод [doSomething] класу [Class01];
- рядок 28: повторюємо ту саму процедуру в [Class03];
Результати
Ми отримали той самий результат, що й із рисою [Trait01], що свідчить про те, що її використання можна уникнути. Ми зазначали, що методи риси можуть працювати з атрибутом [$this→attribut1], навіть якщо сама риса не має цього атрибута. Тож саме класи, які використовують цей трейт, повинні надавати атрибут [$this→attribut1]. Це нетиповий випадок: можна «підняти» атрибут [$this→attribut1], який повинні мати класи, що використовують трейт, безпосередньо в сам трейт. Таким чином, він обов’язково стане частиною атрибутів класу, що використовує трейт.
9.5. Багаторазове успадкування за допомогою риси
У літературі часто можна прочитати, що trait дозволяє здійснювати множинне успадкування: можливість для класу успадковувати від декількох класів. Мова C++ має таку можливість, на відміну від мов Java чи C#, які підтримують лише просте успадкування. Ми покажемо, що хоча використання риси в похідному класі й дозволяє реалізувати щось, схоже на множинне успадкування, цей варіант застосування можна реалізувати й за допомогою простих класів.
Скрипт [trait-06.php] реалізує похідний клас і рис:
<?php
trait Trait01 {
// атрибут
private $i;
// метод для спільного використання
public function doSomethingInTrait01() {
// зміна Trait01::$i
$this->i++;
// відображення
print "Trait01::doSomethingInTrait01… i=$this->i\n";
}
}
class Class02 {
// атрибут
protected $j = 0;
// метод
public function doSomethingInClass02(): void {
// зміна Class02::j
$this->j += 10;
// відображення
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
// похідний клас
class Class03 extends Class02 {
// успадковує від Class02:j та Trait01::i
// включення Trait01
use Trait01;
// метод
public function doSomethingInClass03(): void {
// використання методу з Trait01
$this->doSomethingInTrait01();
// зміна Trait01::i
$this->i += 100;
// зміна Class03::j (==Class02::j)
$this->j += 1000;
// виведення
print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
}
}
// тест ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();
Коментарі
- рядки 3–15: повертаємося до трейсу [Trait01] з атрибутом та методом, що оперує ним;
- рядки 17–29: клас [Class02], який не має нічого спільного з атрибутом [Trait01]. Він його не використовує;
- рядок 19: оголошено єдиний атрибут класу [Class02] із видимістю [protected], щоб він був доступний у похідних класах;
- рядок 32: клас [Class03] успадковує клас [Class02]. Крім того, він включає рис [Trait01] (рядок 35). Нарешті, він успадковує атрибути та методи класу [Class02] і включає атрибути та методи класу [Trait01]. Отже, ми маємо щось аналогічне до множинної спадковості;
Результати
Так само, як це було зроблено в попередньому прикладі, ми покажемо, що:
- трайт можна замінити класом;
- замість того, щоб включати рис у похідний клас, ми включаємо посилання на екземпляр класу;
Скрипт [trait-07.php] має такий вигляд:
<?php
class Class01 {
// атрибут
protected $i;
// геттер і сеттер
public function getI(): int {
return $this->i;
}
public function setI(int $i): void {
$this->i = $i;
}
// метод
public function doSomethingInClass01(): void {
// зміна Class01::$i
$this->i++;
// виведення
print "Class01::doSomething in Class01… i=$this->i\n";
}
}
class Class02 {
// атрибут
protected $j = 0;
// метод класу
public function doSomethingInClass02(): void {
// зміна Class02::j
$this->j += 10;
// виведення
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
class Class03 extends Class02 {
// включення Class01
private $class01;
// сеттер
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// локальний метод класу
public function doSomethingInClass03(): void {
// використання методу класу Class01
$this->class01->doSomethingInClass01();
// зміна Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// зміна Class03::j
$this->j += 1000;
// виведення
print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
}
}
// тест ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();
Коментарі
- рядки 3–24: клас [Class01] замінює рис [Trait01]. Оскільки клас [Class01] буде включено до класів за допомогою посилання, передбачено методи get / set для атрибута [$i];
- рядки 26–38: клас [Classe02] не змінюється;
- рядок 40: клас [Class03] успадковує клас [Classe02];
- рядок 42: клас [Classe01] включено до класу [Classe03] за допомогою посилання;
- рядки 45–47: передбачено клас setter для ініціалізації посилання на клас [Classe01];
- рядки 50–61: метод [doSomethingInClass03] виконує те саме, що й раніше, проте з більш складним кодом;
Результати
З цього прикладу можна зробити висновок, що й у цьому випадку риса не є обов’язковою, але слід визнати, що вона дозволяє писати коротший код у похідному класі.
9.6. Використання трейту замість абстрактного класу
Часто зустрічається такий випадок використання: створюється досить загальний інтерфейс I, який може породити кілька реалізацій. Вони мають спільний код, але відрізняються за іншими методами. Цей випадок використання можна реалізувати двома способами:
- створюється абстрактний клас C, який об’єднує код, спільний для похідних класів. Клас C реалізує інтерфейс I, але деякі методи, які мають бути оголошені в похідних класах, у класі C оголошені як абстрактні, а отже, сам клас C є абстрактним. Потім створюємо класи C1 та C2, похідні від C, які кожен по-своєму реалізують невизначені (абстрактні) методи свого батьківського класу C;
- створюється риса T, майже ідентична абстрактному класу C з попереднього рішення. Ця риса не реалізує інтерфейс I, оскільки синтаксично вона не може цього зробити. Далі створюються класи C1 та C2, які реалізують інтерфейс I та використовують рис T. Цим класам залишається лише реалізувати методи інтерфейсу I, які не реалізовані рисою T, яку вони імпортують;
Ось приклад, який ілюструє велику схожість цих двох рішень.
Додаток 1 реалізує описане вище рішення 1 — [trait-08.php]:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
abstract class AbstractClass implements Interface1 {
// атрибути
protected $attr1 = 11;
protected $attr2 = 12;
// гетери та сеттери
public function getAttr1() {
return $this->attr1;
}
public function getAttr2() {
return $this->attr2;
}
public function setAttr1($attr1) {
$this->attr1 = $attr1;
return $this;
}
public function setAttr2($attr2) {
$this->attr2 = $attr2;
return $this;
}
// реалізований метод
public function doSomething(): void {
print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
}
// нереалізований метод
abstract public function doSomethingElse(): void;
}
// похідний клас 1
class Class1 extends AbstractClass {
// атрибут
private $attr3 = 13;
// геттер і сеттер
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// реалізація doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// похідний клас 2
class Class2 extends AbstractClass {
// атрибут
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// реалізація doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// зовнішня функція
function useInterfaceWith(Interface1 $interface):void{
$interface->doSomething();
$interface->doSomethingElse();
}
// тести
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Коментарі
- рядки 3–8: інтерфейс [Interface1] має два методи;
- рядки 10–41: абстрактний клас [AbstractClass] реалізує інтерфейс [Interface1] (рядок 10). Він має два атрибути з відповідними методами getter та setter (рядки 12–32), реалізує метод [doSomething] інтерфейсу [Interface1] (рядки 35–37) але не може реалізувати метод [doSomethingElse]. Тому цей метод оголошено абстрактним (рядок 40). Абстрактний клас [AbstractClass] не може бути інстанційований, і щоб він мав якесь застосування, його обов’язково потрібно успадкувати;
- рядки 44–63: клас Class1 успадковує абстрактний клас [AbstractClass] і, отже, реалізує інтерфейс [Interface1] (рядок 14). Він надає тіло методу [doSomethingElse], який його батьківський клас не визначив (рядки 59–61). Він також додає атрибут до атрибутів свого батьківського класу (рядки 46–56);
- рядки 66–82: клас Class2 успадковує абстрактний клас [AbstractClass] і, отже, реалізує інтерфейс [Interface1] (рядок 66). Вона надає тіло методу [doSomethingElse], який не був визначений у її батьківському класі (рядки 80–82). Вона також додає атрибут до атрибутів свого батьківського класу (рядки 68–77);
- рядки 87–90: функція [useInterfaceWith] отримує як параметр тип [Interface1] і викликає обидва методи цього інтерфейсу;
- рядки 93–94: функцію [useInterfaceWith] викликають уперше з типом [Class1], а вдруге — з типом [Class2]. Це правильно, оскільки обидва ці типи реалізують інтерфейс [Interface1];
Результати
AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Тепер реалізуємо рішення 2 за допомогою скрипта [trait-09.php]. Воно полягає в заміні абстрактного класу на рис:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
trait Trait1 {
// атрибути
private $attr1 = 11;
private $attr2 = 12;
// гетери та сеттери
public function getAttr1() {
return $this->attr1;
}
public function getAttr2() {
return $this->attr2;
}
public function setAttr1($attr1) {
$this->attr1 = $attr1;
return $this;
}
public function setAttr2($attr2) {
$this->attr2 = $attr2;
return $this;
}
// реалізований метод
public function doSomething(): void {
print "Trait::doSomething [$this->attr1,$this->attr2]\n";
}
}
// похідний клас 1
class Class1 implements Interface1 {
// використання риси
use Trait1;
// атрибут
private $attr3 = 13;
// геттери та сеттери
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// реалізація doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// похідний клас 2
class Class2 implements Interface1 {
// використання риси
use Trait1;
// атрибут
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// реалізація doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// зовнішня функція, що використовує інтерфейс
function useInterfaceWith(Interface1 $interface): void {
$interface->doSomething();
$interface->doSomethingElse();
}
// тестування
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());
Коментарі
- рядки 3–8: інтерфейс [Interface1] не змінився;
- рядки 10–41: рис. [Trait1] замінює абстрактний клас [AbstractClass] із рішення 1. Код є однаковим, за винятком таких деталей:
- рядок 10: риса [Trait1] не реалізує інтерфейс [Interface1]. Це синтаксично неможливо;
- рядки 12–13: атрибут видимості [protected] атрибутів абстрактного класу [AbstractClass] тут стає [private]. Ці два атрибути призначені для надання похідним класам прямого доступу до атрибутів батьківського класу (protected) або риси (private) без необхідності використання геттерів та сеттерів;
- трайт [Trait1] не оголошує абстрактний метод [doSomethingElse];
- рядки 41–62: клас [Class1] з рішення 2 ідентичний класу [Class1] з рішення 1, за винятком таких деталей:
- рядок 41: клас [Class1] реалізує інтерфейс [Interface1], тоді як у рішенні 1 він успадковував абстрактний клас [AbstractClass];
- рядок 43: він використовує властивість [Trait1] для реалізації частини інтерфейсу;
- рядки 65–85: тут можна зробити ті самі зауваження, що й щодо [Class1];
- рядки 87–95: решта коду не змінюється;
Результати
Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]
Ми дійсно отримуємо ті самі результати.
9.7. Conclusion
З наведених прикладів випливає, що випадки, коли використання лінії приносить очевидну користь, не є очевидними. У наших прикладах завжди можна обійтися без неї, замінивши її класом. Однак, здається, що її використання є зручним для факторизації коду між різними похідними класами, ніби цей код належить батьківському класу. Саме це ми й зробимо в наступному прикладі.