Files
php-doc-ru/language/oop5/traits.xml
2021-05-24 13:16:56 +03:00

510 lines
16 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: c94529403818df20d843f8eee6407ab62f341025 Maintainer: mch Status: ready -->
<!-- Reviewed: yes -->
<!-- $Revision$ -->
<sect1 xml:id="language.oop5.traits" xmlns="http://docbook.org/ns/docbook">
<title>Трейты</title>
<para>
PHP реализует метод для повторного использования кода под названием трейт (trait).
</para>
<para>
Трейт - это механизм обеспечения повторного использования кода в языках с
поддержкой только одиночного наследования, таких как PHP. Трейт предназначен для уменьшения
некоторых ограничений одиночного наследования, позволяя разработчику повторно
использовать наборы методов свободно, в нескольких независимых классах и реализованных
с использованием разных архитектур построения классов. Семантика комбинации трейтов и
классов определена таким образом, чтобы снизить уровень сложности, а также избежать
типичных проблем, связанных с множественным наследованием и смешиванием (mixins).
</para>
<para>
Трейт очень похож на класс, но предназначен для группирования функционала хорошо
структурированным и последовательным образом. Невозможно создать самостоятельный
экземпляр трейта. Это дополнение к обычному наследованию и позволяет сделать
горизонтальную композицию поведения, то есть применение членов класса без
необходимости наследования.
</para>
<example xml:id="language.oop5.traits.basicexample">
<title>Пример использования трейта</title>
<programlisting role="php">
<![CDATA[
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
]]>
</programlisting>
</example>
<sect2 xml:id="language.oop5.traits.precedence">
<title>Приоритет</title>
<para>
Наследуемый член из базового класса переопределяется членом, находящимся в трейте.
Порядок приоритета следующий: члены из текущего класса переопределяют методы в
трейте, которые в свою очередь переопределяют унаследованные методы.
</para>
<example xml:id="language.oop5.traits.precedence.examples.ex1">
<title>Пример приоритета старшинства</title>
<para>
Наследуемый метод от базового класса переопределяется методом, вставленным в
MyHelloWorld из трейта SayWorld. Поведение такое же как и для методов, определённых в
классе MyHelloWorld. Порядок приоритета такой: методы из текущего класса
переопределяют методы трейта, которые в свою очередь переопределяют методы из
базового класса.
</para>
<programlisting role="php">
<![CDATA[
<?php
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello World!
]]>
</screen>
</example>
<example xml:id="language.oop5.traits.precedence.examples.ex2">
<title>Пример альтернативного порядка приоритета</title>
<programlisting role="php">
<![CDATA[
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class TheWorldIsNotEnough {
use HelloWorld;
public function sayHello() {
echo 'Hello Universe!';
}
}
$o = new TheWorldIsNotEnough();
$o->sayHello();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello Universe!
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.multiple">
<title>Несколько трейтов</title>
<para>
Несколько трейтов могут быть вставлены в класс путём их перечисления в
директиве use, разделяя запятыми.
</para>
<example xml:id="language.oop5.traits.multiple.ex1">
<title>Пример использования нескольких трейтов</title>
<programlisting role="php">
<![CDATA[
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World';
}
}
class MyHelloWorld {
use Hello, World;
public function sayExclamationMark() {
echo '!';
}
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello World!
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.conflict">
<title>Разрешение конфликтов</title>
<para>
Если два трейта вставляют метод с одним и тем же именем, это приводит к фатальной
ошибке в случае, если конфликт явно не разрешён.
</para>
<para>
Для разрешения конфликтов именования между трейтами, используемыми в одном и том же
классе, необходимо использовать оператор <literal>insteadof</literal> для того, чтобы точно
выбрать один из конфликтующих методов.
</para>
<para>
Так как предыдущий оператор позволяет только исключать методы, оператор
<literal>as</literal> может быть использован для включения одного из конфликтующих
методов под другим именем. Обратите внимание, что оператор <literal>as</literal> не
переименовывает метод и не влияет на какой-либо другой метод.
</para>
<example xml:id="language.oop5.traits.conflict.ex1">
<title>Пример разрешения конфликтов</title>
<para>
В этом примере Talker использует трейты A и B.
Так как в A и B есть конфликтующие методы, он определяет использовать
вариант smallTalk из трейта B, а вариант bigTalk - из трейта A.
</para>
<para>
Класс Aliased_Talker применяет оператор <literal>as</literal>
чтобы получить возможность использовать имплементацию bigTalk из B
под дополнительным псевдонимом <literal>talk</literal>.
</para>
<programlisting role="php">
<![CDATA[
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
class Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.visibility">
<title>Изменение видимости метода</title>
<para>
Используя синтаксис оператора <literal>as</literal> можно также настроить
видимость метода в использующем трейт классе.
</para>
<example xml:id="language.oop5.traits.visibility.ex1">
<title>Пример изменения видимости метода</title>
<programlisting role="php">
<![CDATA[
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// Изменение видимости метода sayHello
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// Создание псевдонима метода с изменённой видимостью
// видимость sayHello не изменилась
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.composition">
<title>Трейты, состоящие из трейтов</title>
<para>
Аналогично тому, как классы могут использовать трейты, также и трейты могут использовать
другие трейты. Используя один или более трейтов в определении другого трейта, он может
частично или полностью состоять из членов, определённых в этих трейтах.
</para>
<example xml:id="language.oop5.traits.composition.ex1">
<title>Пример трейтов, составленных из трейтов</title>
<programlisting role="php">
<![CDATA[
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World!';
}
}
trait HelloWorld {
use Hello, World;
}
class MyHelloWorld {
use HelloWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hello World!
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.abstract">
<title>Абстрактные члены трейтов</title>
<para>
Трейты поддерживают использование абстрактных методов для того, чтобы установить требования
к использующему классу.
Поддерживаются общедоступные, защищённые и закрытые методы.
До PHP 8.0.0 поддерживались только общедоступные и защищённые абстрактные методы.
</para>
<caution>
<simpara>
Конкретный класс исполняет эти требования путём определения конкретного
метода с тем же именем; при этом сигнатура метода может отличаться.
</simpara>
</caution>
<example xml:id="language.oop5.traits.abstract.ex1">
<title>Требования трейта при помощи абстрактных методов</title>
<programlisting role="php">
<![CDATA[
<?php
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
}
class MyHelloWorld {
private $world;
use Hello;
public function getWorld() {
return $this->world;
}
public function setWorld($val) {
$this->world = $val;
}
}
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.static">
<title>Статические члены трейта</title>
<para>
В трейтах можно определять статические переменные, статические методы и статические свойства.
</para>
<example xml:id="language.oop5.traits.static.ex1">
<title>Статические переменные</title>
<programlisting role="php">
<![CDATA[
<?php
trait Counter {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
class C1 {
use Counter;
}
class C2 {
use Counter;
}
$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>
]]>
</programlisting>
</example>
<example xml:id="language.oop5.traits.static.ex2">
<title>Статические методы</title>
<programlisting role="php">
<![CDATA[
<?php
trait StaticExample {
public static function doSomething() {
return 'Что-либо делаем';
}
}
class Example {
use StaticExample;
}
Example::doSomething();
?>
]]>
</programlisting>
</example>
<example xml:id="language.oop5.traits.static.ex3">
<title>Статические свойства</title>
<programlisting role="php">
<![CDATA[
<?php
trait StaticExample {
public static $static = 'foo';
}
class Example {
use StaticExample;
}
echo Example::$static;
?>
]]>
</programlisting>
</example>
</sect2>
<sect2 xml:id="language.oop5.traits.properties">
<title>Свойства</title>
<para>
Трейты могут также определять свойства.
</para>
<example xml:id="language.oop5.traits.properties.example">
<title>Определение свойств</title>
<programlisting role="php">
<![CDATA[
<?php
trait PropertiesTrait {
public $x = 1;
}
class PropertiesExample {
use PropertiesTrait;
}
$example = new PropertiesExample;
$example->x;
?>
]]>
</programlisting>
</example>
<para>
Если трейт определяет свойство, то класс не может определить свойство с
таким же именем, кроме случаев полного совпадения (те же начальное значение и
модификатор видимости), иначе будет сгенерирована фатальная ошибка.
</para>
<example xml:id="language.oop5.traits.properties.conflicts">
<title>Разрешение конфликтов</title>
<programlisting role="php">
<![CDATA[
<?php
trait PropertiesTrait {
public $same = true;
public $different = false;
}
class PropertiesExample {
use PropertiesTrait;
public $same = true;
public $different = true; // Фатальная ошибка
}
?>
]]>
</programlisting>
</example>
</sect2>
</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->