belarusweb.net
© Петр Романовский Минск, 2016-2017.



belarusweb.net

Основы создания сайтов...

PHP+MySQL >>>

                          Учебник Задачник Справочник

6.6. Специальные виды классов

В PHP поддерживается определение абстрактных классов и методов, т.е. некоторых наиболее общих шаблонов, которые в основном имеют описательный смысл и задают характерные особенности всех наследуемых классов-потомков. При этом, если в классе объявляется хотя бы один абстрактный метод, то и сам класс должен быть объявлен абстрактным. Также нельзя создавать экземпляры (объекты) абстрактного класса, а методы, объявленные абстрактными, не должны включать конкретной реализации. Объявляются абстрактные классы и методы при помощи служебного слова abstract (см. пример 6.11).

<?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>';
   }
//Расширяем область видимости метода и добавляем ему один необязательный параметр 
	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(' Петр ', ' Сидоров');
?>

Пример 6.11. Использование абстрактных классов и методов

При наследовании от абстрактного класса все методы, объявленные абстрактными в родительском классе, должны быть определены и в классе-потомке, а их область видимости, как и в случае с обычными методами, должна оставаться той же или быть менее строгой. Например, если абстрактный метод был объявлен как protected, то область видимости реализации этого метода в классе-потомке должна быть либо protected либо public, но никак не private. Также следует помнить, что должны совпадать типы данных при использовании контроля типов и количество обязательных аргументов метода. И хотя разрешается добавлять необязательные аргументы (т.е. аргументы, которые используют значения по умолчанию), количество обязательных аргументов в любом случае должно совпадать (см. пример 6.11).

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

Объявляются интерфейсы при помощи отдельного ключевого слова interface, но в случае необходимости расширения, могут быть унаследованы друг от друга также, как и обычные классы, т.е. при помощи оператора extends. Если же интерфейс наследуется (реализуется) обычным классом, а не другим интерфейсом, должен использоваться не оператор extends, а специально предназначенный для такого случая оператор implements (см. пример 6.12).

<?php
interface my_intf_1{								//Объявляем первый интерфейс
   const c_1="Константа интерфейса 1";		//Объявляем константу интерфейса
	public function my_func_1($a, $b);		//Метод должен принимать 2 аргумента
}

interface my_intf_2{								//Объявляем второй интерфейс
   public function my_func_2($d);			//Метод должен принимать 1 аргумент
}

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();			 
$obj->my_func_2(5);			 							  //Выведет '10'
$obj->my_func_3();										  //Выведет 'Строка из my_func_4'
echo $obj::c_1.'<br>'.$obj::c_4.'<br>'.$obj::c_2; //Выведет все константы
?> 

Пример 6.12. Использование интерфейсов

Реализовывать в одном классе два интерфейса, содержащих одноименную функцию, запрещено, т.к. это повлечет за собой неоднозначность. Кроме того, сигнатуры методов в классе, реализующем интерфейс, должны точно совпадать с сигнатурами, используемыми в интерфейсе, иначе будет вызвана ошибка.

Отметим, что интерфейсы могут также содержать константы, которые работают так же, как и константы обычных классов, за исключением того, что они не могут быть переопределены наследующим классом или интерфейсом.

Для уменьшения некоторых ограничений единого наследования в PHP используются специальные конструкции, называемые трейтами, которые представляют собой наборы свойств и методов доступных для последующего свободного и многократного использования сразу в нескольких независимых классах. Трейты очень похожи на классы, но не могут иметь собственных объектов и дают возможность использовать свои свойства и методы классами свободно без необходимости наследования (см. пример 6.13).

<?php
trait tr_1{				  //Трейты объявляются при помощи служебного слова trait
	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>';
	}
}

$obj_cld_cl_1=new child_class_1();  //Создали объект класса-потомка child_class_1
$obj_cld_cl_1->func_tr_1();         //Выведет 'Метод 1-го трейта'
$obj_cld_cl_1::func_tr_2();         //Выведет 'Статический метод 2-го трейта'
$obj_cld_cl_1->func_bs_cl_1();      //Выведет 'Метод класса base_class_1'
$obj_cld_cl_1->func_cld_cl_1();     //Выведет 'Метод класса-потомка child_class_1'
echo $obj_cld_cl_1->var_tr_1.'<br>'; 	 //Выведет 'Свойство 1-го трейта'
echo $obj_cld_cl_1::$var_tr_2.'<br>';   //Выведет 'Статическое свойство 2-го трейта'

$obj_bs_cl_2=new base_class_2();    //Создали объект класса base_class_2
$obj_bs_cl_2->func_tr_3();          //Выведет 'Реализация абстр. метода func_tr_3'
$obj_bs_cl_2->func_tr_4();          //Выведет 'Реализация абстр. метода func_tr_4'
echo $obj_bs_cl_2->var_bs_cl_2.'<br>';  //Выведет 'Св-во 2-го класса'
	/* Плюс свойства и методы всех подключенных ко второму классу трейтов */
$obj_bs_cl_2->func_tr_1();          //Выведет 'Метод 1-го трейта'
$obj_bs_cl_2::func_tr_2();          //Выведет 'Статический метод 2-го трейта'
echo $obj_bs_cl_2->var_tr_1.'<br>'; //Выведет 'Свойство 1-го трейта'
echo $obj_bs_cl_2::$var_tr_2;			//Выведет 'Статическое свойство 2-го трейта'
?> 

Пример 6.13. Использование трейтов

Как было показано в примере, трейты объявляются и подключаются, соответственно, при помощи ключевых слов trait и use. В случае подключения нескольких трейтов сразу, они перечисляются через запятую. При этом разрешается использовать трейты не только в классах, но и подключать их к другим трейтам, при помощи того же оператора use.

При формировании трейтов разрешается использовать абстрактные методы, а также статические свойства и методы, но объявлять константы в трейтах нельзя.

Следует помнить, что область видимости всех методов и свойств в трейтах должна быть определена как public, а далее, при использовании в классах, она может быть изменена в зависимости от конкретных потребностей класса.

В случае объявления в классе свойства с именем, совпадающим с именем свойства присутствующего в подключаемом трейте, возникнет ошибка. Тоже самое произойдет и при подключении к классу двух трейтов, имеющих методы с одинаковыми именами. Однако в этом случае ошибки можно избежать, использовав оператор insteadof, который позволяет выбрать из конфликтных методов лишь один. Если же необходимо включить в класс сразу оба метода, следует использовать один из них под другим именем при помощи оператора as, который к тому же позволяет изменить и область видимости подключаемого метода (см. пример 6.14).

<?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{					 					 //Объявили второй трейт
	public function func_a(){ 				 //Имя используется и в tr_1
		echo 'Метод func_a 2-го трейта'.'<br>';
	}
	public function func_b(){ 				 //Имя используется и в tr_1
		echo 'Метод func_b 2-го трейта'.'<br>';
	}
	public function func_c(){ 				 //Еще один метод 2-го трейта
		echo 'Метод func_c 2-го трейта'.'<br>';
	}
}

class base_class{		 						 //Объявили класс
// public $var_tr_1='Свойство класса';  //Ошибка, свойство уже определено в трейте
	use tr_1, tr_2{
		tr_1::func_a insteadof tr_2;		 //Будем использовать метод первого трейта
		tr_1::func_a as protected; 		 //Переопределяем его на protected 
		tr_2::func_b insteadof tr_1;		 //Будем использовать метод второго трейта
		tr_2::func_a as protected func_a_2;//Метод 2-го трейта используем под именем
	}												 //func_a_2 и с областью видимости protected
	public function func_c(){				 //Объявили собственный метод класса
													 //который переопределит метод 2-го трейта
		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>';  
	}
}

$obj_bs_cl=new base_class();    //Создали объект класса base_class
$obj_bs_cl->func_b();           //Выведет 'Метод func_b 2-го трейта '
$obj_bs_cl->func_c();           //Выведет 'Метод класса base_class '

$obj_cld_cl=new child_class();  //Создали объект класса-потомка 
$obj_cld_cl->func_b();          //Выведет 'Метод класса child_class '
$obj_cld_cl->func_d();          //Выведет 'Метод func_a 1-го трейта '
$obj_cld_cl->func_e();          //Выведет 'Метод func_a 2-го трейта '
$obj_cld_cl->func_c();          //Выведет 'Метод класса base_class '
?>

Пример 6.14. Разрешение конфликтов в трейтах

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

В PHP 7 была добавлена поддержка анонимных классов, которые могут быть полезны в случае необходимости одноразового использования, например, для возврата функцией значения в виде определенного объекта с требуемым набором свойств и методов. При этом анонимные классы могут иметь конструкторы, расширять другие классы, реализовывать интерфейсы или подключать трейты точно также, как и обычные классы (см. пример 6.15).

<?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>';	
	}
};														 //Не забываем про точку с запятой!!!

$obj_5->_5_minus(6);				//Выведет '-1'
$obj_5->_5_plus(6);				//Выведет '11'
$obj_5->_5_mult(6);				//Выведет '30'
?> 

Пример 6.15. Анонимные классы

Анонимные классы могут использоваться также внутри других классов. При этом в общем случае у них отсутствует доступ к свойствам и методам внешнего класса, которые имеют область видимости private или protected. Поэтому, чтобы получить доступ к ним, анонимный класс должен расширять внешний класс. В этом случае закрытые и защищенные методы становятся доступны напрямую, а свойства должны передаваться анонимному классу через его конструктор (см. пример 6.16).

<?php
class outer_class{ 					//Создаем внешний класс
	protected $n_out=2;		 		//Объявили свойство внешнего класса
		
	public function func_ins_1(){	//Определяем первый метод	outer_class
		//Здесь $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>'		//И сразу же обращаемся к свойству созданного 
	}										//объекта анонимного класса				
		
	public function func_ins_2(){ //Определяем второй метод	outer_class	
		//Здесь $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();
$obj->func_ins_1();							//Выведет '4'
echo $obj->func_ins_2()->n_ins.'<br>';	//Выведет '4'
echo $obj->func_ins_2()::a;				//Выведет '5'
?> 

Пример 6.16. Использование анонимных классов внутри других классов

Таким образом, конструкция (new class(){}); представляет собой неименованный объект с определенным набором свойств и методов, который может быть использован как сразу в паре с различными операторами, например, echo или return, так и сохранен в обычной переменной для доступа к его свойствам и методам позднее.

Контрольные вопросы и задания

  1. Опишите абстрактные классы, интерфейсы, трейты.
  2. Какие классы называют анонимными? Опишите их синтаксис и особенности использования.

Словарь новых английских слов

one-off [wʌnˈɒf] – одноразовый.
partial [ˈpɑːʃ(ə)l] – частичный.
class [klɑːs] – класс.
new [njuː] – новый.
empty [ˈempti] – пустой.
constant [ˈkɒnst(ə)nt] – константа.
person [ˈpəːs(ə)n] – личность, персона.
object [ˈɒbdʒɪkt] – объект.
method [ˈmɛθəd] – метод.
property [ˈprɒpəti] – свойство.
public [ˈpʌblɪk] – открытый, общественный.
protected [prəˈtektɪd] – защищенный.
private [ˈprʌɪvət] – закрытый, частный.
this [ðɪs] – это, этот.
static [ˈstatɪk] – статичный, неподвижный.
extend [ɪkˈstɛnd] – расширять.
self [self] – собственный.
parent [ˈpɛːr(ə)nt] – родитель, предок.
exit [ˈeksɪt] – выход, выйти.
nest [nest] – вставлять один предмет в другой.
final [ˈfʌɪn(ə)l] – заключительный, окончательный.
construct [kənˈstrʌkt] – создавать, конструировать.
destruct [dɪˈstrʌkt] – разрушать, уничтожать.
descendant [dɪˈsɛnd(ə)nt] – потомок.
abstract [ˈæbstrækt] – абстрактный, отвлеченный.
interface [ˈɪntəfeɪs] – интерфейс, область контакта.
implement [ˈɪmplɪment] – осуществлять, реализовывать.
trait [treɪt] – особенность, черта.
use [ˈjuːs] – употреблять, использовать.
instead [ɪnˈstɛd] – вместо, взамен.
as [æz] – как.
anonymous [əˈnɒnɪməs] – безымянный, анонимный.
clone [kləʊn] – клон, двойник.
set [set] – устанавливать.
unset [ʌnˈsɛt] – удалять.
get [ɡet] – получать.
call [kɔːl] – вызывать, звать.

Комментарии (0)
Петр Романовский
1. Приветствуются комментарии, которые содержат дополнения к материалу текущей страницы, а также ваши ответы на вопросы других пользователей.
2. Если вам что-то непонятно - спрашивайте, не забывая написать, что именно и с какого места.
Показаны все комментарии
Чтобы оставить свой комментарий, авторизуйтесь, пожалуйста!    
     
belarusweb.net © Петр Романовский, Минск, 2016-2017.
Связаться с автором
Наверх