PHP :: Специальные виды классов
Абстрактные классы в PHP
В PHP поддерживается определение абстрактных классов и методов, т.е. некоторых наиболее общих шаблонов, которые в основном имеют описательный смысл и задают характерные особенности всех наследуемых классов-потомков. При этом, если в классе объявляется хотя бы один абстрактный метод, то и сам класс должен быть объявлен абстрактным. Также нельзя создавать экземпляры (объекты) абстрактного класса, а методы, объявленные абстрактными, не должны включать конкретной реализации. Объявляются абстрактные классы и методы при помощи служебного слова abstract (см. пример №1).
htmlCodes
<?php
//Объявили абстрактный класс
abstract class abstract_class{
//Абстрактный, т.е. без реализации
abstract protected function return_value();
//Абстрактный, т.е. 'пустой'
abstract protected function get_name($name);
//Обычный общий метод наследуемый
public function common_method(){
//всеми классами-потомками
return $this->return_value();
}
}
class concrete_class_1 extends abstract_class{
//Реализуем (заполняем) метод
protected function return_value() {
return 'Реализация метода в классе-потомке concrete_class_1'.'<br>';
}
//Расширяем область видимости метода
public function get_name($first_name){
return "{$first_name}".'<br>';
}
}
class concrete_class_2 extends abstract_class{
//Расширяем область видимости метода
public function return_value(){
return 'Реализация метода в классе-потомке concrete_class_2'.'<br>';
}
//Расширяем область видимости метода и добавляем 1 необязательный параметр
public function get_name($first_name, $last_name=' Иванов '){
return "{$first_name} {$last_name}".'<br>';
}
}
//Создаем экземпляр первого класса-потомка
$obj_1 = new concrete_class_1;
echo $obj_1->common_method();
echo $obj_1->get_name(' Иван ');
//Создаем экземпляр второго класса-потомка
$obj_2 = new concrete_class_2;
echo $obj_2->common_method();
//Используется значение по умолчанию
echo $obj_2->get_name(' Петр ');
echo $obj_2->get_name(' Петр ', ' Сидоров');
?>
Пример №1. Использование абстрактных классов и методов
При наследовании от абстрактного класса все методы, объявленные абстрактными в родительском классе, должны быть определены и в классе-потомке, а их область видимости, как и в случае с обычными методами, должна оставаться той же или быть менее строгой. Например, если абстрактный метод был объявлен как protected, то область видимости реализации этого метода в классе-потомке должна быть либо protected либо public, но никак не private. Также следует помнить, что должны совпадать типы данных при использовании контроля типов и количество обязательных аргументов метода. И хотя разрешается добавлять необязательные аргументы (т.е. аргументы, которые используют значения по умолчанию), количество обязательных аргументов в любом случае должно совпадать (см. пример №1).
Интерфейсы в PHP
Еще одной разновидностью общих шаблонов, использующихся в PHP, являются интерфейсы. Они служат для указания методов без необходимости описывания их функционала. При этом все методы, определенные в интерфейсе, должны быть реализованы в наследующих данный интерфейс классах, иметь область видимости public (и в интерфейсе, и в классе), а их тела должны быть пустыми. Но самой важной особенностью интерфейсов является то, что классы могут реализовывать (наследовать) сразу несколько интерфейсов, перечисляемых при наследовании через запятую (соответственно, должны быть реализованы и все методы наследуемых интерфейсов).
Объявляются интерфейсы при помощи отдельного ключевого слова interface, но в случае необходимости расширения, могут быть унаследованы друг от друга также, как и обычные классы, т.е. при помощи оператора extends. Если же интерфейс наследуется (реализуется) обычным классом, а не другим интерфейсом, должен использоваться не оператор extends, а специально предназначенный для такого случая оператор implements (см. пример №2).
htmlCodes
<?php
//Объявляем первый интерфейс
interface my_intf_1{
//Объявляем константу интерфейса
const c_1="Константа интерфейса 1";
//Метод должен принимать 2 аргумента
public function my_func_1($a, $b);
}
//Объявляем второй интерфейс
interface my_intf_2{
//Метод должен принимать 1 аргумент
public function my_func_2($d);
}
//Расширяем интерфейс
interface my_intf_3 extends my_intf_1, my_intf_2{
//Метод без аргументов
public function my_func_3();
}
//Объявляем еще один интерфейс
interface my_intf_4{
//Объявляем константу интерфейса
const c_4="Константа интерфейса 4";
}
//Реализуем расширенный интерфейс
class my_class implements my_intf_3, my_intf_4{
//Объявляем константу класса
const c_2="Константа класса";
//Реализуем первый метод
public function my_func_1($a, $b){
return $a+$b;
}
//Реализуем второй метод
public function my_func_2($d){
echo $this->my_func_1($d,5).'<br>';
}
//Реализуем третий метод
public function my_func_3(){
$this->my_func_4();
}
//Объявляем закрытый метод класса
private function my_func_4(){
echo 'Строка из my_func_4'.'<br>';
}
}
$obj= new my_class();
//Выведет '10'
$obj->my_func_2(5);
//Выведет 'Строка из my_func_4'
$obj->my_func_3();
//Выведет все константы
echo $obj::c_1.'<br>'.$obj::c_4.'<br>'.$obj::c_2;
?>
Пример №2. Использование интерфейсов
Реализовывать в одном классе два интерфейса, содержащих одноименную функцию, запрещено, т.к. это повлечет за собой неоднозначность. Кроме того, сигнатуры методов в классе, реализующем интерфейс, должны точно совпадать с сигнатурами, используемыми в интерфейсе, иначе будет вызвана ошибка.
Отметим, что интерфейсы могут также содержать константы, которые работают так же, как и константы обычных классов, за исключением того, что они не могут быть переопределены наследующим классом или интерфейсом.
Трейты в PHP
Для уменьшения некоторых ограничений единого наследования в PHP используются специальные конструкции, называемые трейтами, которые представляют собой наборы свойств и методов доступных для последующего свободного и многократного использования сразу в нескольких независимых классах. Трейты очень похожи на классы, но не могут иметь собственных объектов и дают возможность использовать свои свойства и методы классами свободно без необходимости наследования (см. пример №3).
htmlCodes
<?php
//Трейты объявляются при помощи служебного слова trait
trait tr_1{
//Объявили свойство трейта
public $var_tr_1='Свойство 1-го трейта';
//Объявили метод трейта
public function func_tr_1(){
echo 'Метод 1-го трейта'.'<br>';
}
}
//Объявили второй трейт
trait tr_2{
static $var_tr_2='Статическое свойство 2-го трейта ';
//Объявили статический метод трейта
public static function func_tr_2(){
echo 'Статический метод 2-го трейта'.'<br>';
}
}
//Объявили третий трейт
trait tr_3{
//Подключаем первый и второй трейты
use tr_1, tr_2;
//Объявили абстрактный метод трейта
public abstract function func_tr_3();
}
//Объявили четвертый трейт
trait tr_4{
//Объявили абстрактный метод трейта
public abstract function func_tr_4();
}
//Объявили первый класс
class base_class_1{
//Подключаем первый трейт
use tr_1;
//Объявили метод первого класса
public function func_bs_cl_1(){
echo 'Метод класса base_class_1'.'<br>';
}
}
//Объявили класс-потомок первого класса
class child_class_1 extends base_class_1{
//Подключаем второй трейт
use tr_2;
//Объявили метод класса
public function func_cld_cl_1(){
echo 'Метод класса-потомка child_class_1'.'<br>';
}
}
//Объявили второй класс
class base_class_2{
//Объявили свойство второго класса
public $var_bs_cl_2='Св-во 2-го класса';
//Подключаем третий и четвертый трейты
use tr_3,tr_4;
//Абстрактный метод должен быть реализован
public function func_tr_3(){
echo 'Реализация абстр. метода func_tr_3'.'<br>';
}
//Абстрактный метод должен быть реализован
public function func_tr_4(){
echo 'Реализация абстр. метода func_tr_4'.'<br>';
}
}
//Создали объект класса-потомка child_class_1
$obj_cld_cl_1=new child_class_1();
//Выведет 'Метод 1-го трейта'
$obj_cld_cl_1->func_tr_1();
//Выведет 'Статический метод 2-го трейта'
$obj_cld_cl_1::func_tr_2();
//Выведет 'Метод класса base_class_1'
$obj_cld_cl_1->func_bs_cl_1();
//Выведет 'Метод класса-потомка child_class_1'
$obj_cld_cl_1->func_cld_cl_1();
//Выведет 'Свойство 1-го трейта'
echo $obj_cld_cl_1->var_tr_1.'<br>';
//Выведет 'Статическое свойство 2-го трейта'
echo $obj_cld_cl_1::$var_tr_2.'<br>';
//Создали объект класса base_class_2
$obj_bs_cl_2=new base_class_2();
//Выведет 'Реализация абстр. метода func_tr_3'
$obj_bs_cl_2->func_tr_3();
//Выведет 'Реализация абстр. метода func_tr_4'
$obj_bs_cl_2->func_tr_4();
//Выведет 'Св-во 2-го класса'
echo $obj_bs_cl_2->var_bs_cl_2.'<br>';
//Плюс свойства и методы всех подключенных ко второму классу трейтов
//Выведет 'Метод 1-го трейта
$obj_bs_cl_2->func_tr_1(); '
//Выведет 'Статический метод 2-го трейта'
$obj_bs_cl_2::func_tr_2();
//Выведет 'Свойство 1-го трейта'
echo $obj_bs_cl_2->var_tr_1.'<br>';
//Выведет 'Статическое свойство 2-го трейта'
echo $obj_bs_cl_2::$var_tr_2;
?>
Пример №3. Использование трейтов
Как было показано в примере, трейты объявляются и подключаются, соответственно, при помощи ключевых слов trait и use. В случае подключения нескольких трейтов сразу, они перечисляются через запятую. При этом разрешается использовать трейты не только в классах, но и подключать их к другим трейтам, при помощи того же оператора use.
При формировании трейтов разрешается использовать абстрактные методы, а также статические свойства и методы, но объявлять константы в трейтах нельзя.
Следует помнить, что область видимости всех методов и свойств в трейтах должна быть определена как public, а далее, при использовании в классах, она может быть изменена в зависимости от конкретных потребностей класса.
В случае объявления в классе свойства с именем, совпадающим с именем свойства присутствующего в подключаемом трейте, возникнет ошибка. Тоже самое произойдет и при подключении к классу двух трейтов, имеющих методы с одинаковыми именами. Однако в этом случае ошибки можно избежать, использовав оператор insteadof, который позволяет выбрать из конфликтных методов лишь один. Если же необходимо включить в класс сразу оба метода, следует использовать один из них под другим именем при помощи оператора as, который к тому же позволяет изменить и область видимости подключаемого метода (см. пример №4).
htmlCodes
<?php
trait tr_1{
//Объявили свойство трейта
public $var_tr_1='Св-во трейта';
//Объявили метод трейта
public function func_a(){
echo 'Метод func_a 1-го трейта'.'<br>';
}
//Объявили метод трейта
public function func_b(){
echo 'Метод func_b 1-го трейта'.'<br>';
}
}
//Объявили второй трейт
trait tr_2{
//Имя используется и в tr_1
public function func_a(){
echo 'Метод func_a 2-го трейта'.'<br>';
}
//Имя используется и в tr_1
public function func_b(){
echo 'Метод func_b 2-го трейта'.'<br>';
}
//Еще один метод 2-го трейта
public function func_c(){
echo 'Метод func_c 2-го трейта'.'<br>';
}
}
//Объявили класс
class base_class{
//Ошибка, свойство уже определено в трейте
// public $var_tr_1='Свойство класса';
use tr_1, tr_2{
//Будем использовать метод первого трейта
tr_1::func_a insteadof tr_2;
//Переопределяем его на protected
tr_1::func_a as protected;
//Будем использовать метод второго трейта
tr_2::func_b insteadof tr_1;
//Метод 2-го трейта используем под именем
//func_a_2 с областью видимости protected
tr_2::func_a as protected func_a_2;
}
//Объявили собственный метод класса,который переопределит метод 2-го трейта
public function func_c(){
echo 'Метод класса base_class'.'<br>';
}
}
//Объявили класс-потомок
class child_class extends base_class{
//Объявили собственный метод класса-потомка
public function func_d(){
//Вызываем метод родительского класса
parent::func_a();
}
//Объявили еще один метод класса-потомка
public function func_e(){
//Вызываем метод родительского класса
parent::func_a_2();
}
//Переопределяем метод родительского класса
public function func_b(){
echo 'Метод класса child_class'.'<br>';
}
}
//Создали объект класса base_class
$obj_bs_cl=new base_class();
//Выведет 'Метод func_b 2-го трейта '
$obj_bs_cl->func_b();
//Выведет 'Метод класса base_class '
$obj_bs_cl->func_c();
//Создали объект класса-потомка
$obj_cld_cl=new child_class();
//Выведет 'Метод класса child_class '
$obj_cld_cl->func_b();
//Выведет 'Метод func_a 1-го трейта '
$obj_cld_cl->func_d();
//Выведет 'Метод func_a 2-го трейта '
$obj_cld_cl->func_e();
//Выведет 'Метод класса base_class '
$obj_cld_cl->func_c();
?>
Пример №4. Разрешение конфликтов в трейтах
Обратите внимание, что в примере трейт, который был использован в родительском классе, не переопределяет одноименный метод этого класса. Если же трейт вставляется в класс-потомок, то методы трейта, наоборот, имеют больший приоритет и переопределяют одноименные унаследованные методы родительского класса, хотя собственные методы текущего класса-потомка они по-прежнему не переопределяют.
Анонимные классы в PHP
В PHP 7 была добавлена поддержка анонимных классов, которые могут быть полезны в случае необходимости одноразового использования, например, для возврата функцией значения в виде определенного объекта с требуемым набором свойств и методов. При этом анонимные классы могут иметь конструкторы, расширять другие классы, реализовывать интерфейсы или подключать трейты точно также, как и обычные классы (см. пример №5).
htmlCodes
<?php
//Создаем трейт
trait tr_5_minus{
//Определяем один метод
public function _5_minus($n){
echo $this->m-$n.'<br>';
}
}
//Создаем интерфейс
interface int_5_mult{
//Задаем шаблон метода
public function _5_plus($n);
}
//Создаем объект анонимного класса
$obj_5=new class(5) implements int_5_mult{
//Подключаем интерфейс
use tr_5_minus;
//Объявляем свойство класса
public $m;
//Объявляем конструктор класса
function __construct($arg){
//Устанавливает первый член операций
$this->m=$arg;
}
//Реализуем интерфейс
public function _5_plus($n){
echo $this->m+$n.'<br>';
}
//Создаем собственный метод
public function _5_mult($n){
echo $this->m*$n.'<br>';
}
}; //Не забываем про точку с запятой!!!
//Выведет '-1'
$obj_5->_5_minus(6);
//Выведет '11'
$obj_5->_5_plus(6);
//Выведет '30'
$obj_5->_5_mult(6);
?>
Пример №5. Анонимные классы
Анонимные классы могут использоваться также внутри других классов. При этом в общем случае у них отсутствует доступ к свойствам и методам внешнего класса, которые имеют область видимости private или protected. Поэтому, чтобы получить доступ к ним, анонимный класс должен расширять внешний класс. В этом случае закрытые и защищенные методы становятся доступны напрямую, а свойства должны передаваться анонимному классу через его конструктор (см. пример №6).
htmlCodes
<?php
//Создаем внешний класс
class outer_class{
//Объявили свойство внешнего класса
protected $n_out=2;
//Определяем первый метод outer_class
public function func_ins_1(){
//Здесь $this представляет внешний класс
echo (new class($this->n_out) extends outer_class{
//Объявили свойство анонимного класса
public $n_ins;
//Конструктор
function __construct($arg){
//А здесь $this уже относится к анонимному классу
$this->n_ins=$arg*$arg;
}
//И сразу же обращаемся к свойству созданного
//объекта анонимного класса
})->n_ins;.'<br>'
}
//Определяем второй метод outer_class
public function func_ins_2(){
//Здесь $this представляет внешний класс
return new class($this->n_out) extends outer_class{
//Объявили константу анонимного класса
const a=5;
//Объявили свойство анонимного класса
public $n_ins;
//Конструктор
function __construct($arg){
//А здесь $this уже относится к анонимному классу
$this->n_ins=$arg*$arg;
}
}; //Возвращаем готовый анонимный объект
}
}
$obj=new outer_class();
//Выведет '4'
$obj->func_ins_1();
//Выведет '4'
echo $obj->func_ins_2()->n_ins.'<br>';
//Выведет '5'
echo $obj->func_ins_2()::a;
?>
Пример №6. Использование анонимных классов внутри других классов
Таким образом, конструкция (new class(){}); представляет собой неименованный объект с определенным набором свойств и методов, который может быть использован как сразу в паре с различными операторами, например, echo или return, так и сохранен в обычной переменной для доступа к его свойствам и методам позднее.
Быстрый переход к другим страницам
- Определение области видимости свойств и методов в PHP
- Специальные виды классов в PHP
- Операции с объектами в PHP
- Вернуться к оглавлению учебника