Блог о программировании

Особенности ООП в PHP: traits

Категория: PHP
 23 февраля 2017 г. 20:37

Данная статья является логическим продолжением статьи о трейтах, выложенной на официальном сайте php. Начиная с версии PHP 5.4.0 появилась такая конструкция как трейты. Прошло уже более 4х лет с момента появления трейтов, тем не менее очень часто приходится сталкиваться с неясностью со стороны других разработчиков по поводу использования трейтов. Многие попросту не понимают, когда их нужно использовать и нужно ли вообще. В интернете полно информации о том, как их писать, объявлять, есть информация об особенностях поведения трейтов. Однако практически не раскрытым остается вопрос о том, в каких же случаях уместо их использовать. В данной статье постараюсь ответить на этот вопрос. Поскольку трейты уже не являются новинкой для языка PHP, можно обощить полученный опыт их использования и описать практики ввнедрения данного механизма.


Трейты и множественное наследование

Во многих источниках о трейтах пишется, что они выполняют роль множественного наследования. Это не так! При наследовании подразумевается, что один класс является родительским, а другой - потомком. Для выявления таких случаев в php предусмотрен оператор instanceof, который работает с классами и не работает с трейтами. Наследование подразумевает наличие механизма полиморфизма, трейты же - это механизм копирования функциональности. Наследование позволяет создавать иерархии абстракций, трейты - горизонтальное расширение функциональности класса.


Использование трейтов

Способ 1

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

trait Singleton 
{
    /**
     * get class instance
     *
     * @return object
     */

    final public static function getInstance() {
        static $self = null;

        if (is_null($self)) {
            $self = new self;
            call_user_func_array([$self, "init"], func_get_args());
        }

        return $self;
    }

    /**
     * method called after parent object
     * instantiation with primary input
     * parameters provided.
     * Method could be extended
     */
    protected function init() {
        
    }

    /**
     * do nothing here, extend init() instead
     *
     * @return object
     */
    final private function __construct() {
        
    }

    private function __clone() {
        throw new Exception('Cloning ' . __CLASS__ . ' is not allowed.');
    }

    private function __wakeup() {
        throw new Exception('Cloning ' . __CLASS__ . ' is not allowed.');
    }
}

Действительно, при таком использовании трейты становятся очень удобными - одна строчка в коде, и класс уже является синглтоном:

class HelloWorld 
{
    use Singleton;
    
    public function displayText() {
        echo 'Hello World!';
    }
}
HelloWorld::getInstance()->displayText();

Результат выполнения кода:

Hello World!

Способ 2

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

Например, допустим необходимо реализовать шаблон проектирования Observer. Довольно распространенный шаблон, кстати. С помощью трейтов этот шаблон имеет очень элегантное решение:

trait Notifier 
{
    protected $listeners = array();

    protected function addListener(IListener $listener) {
        $this->listeners[] = $listener;
        return $this;
    }

    protected function clearListeners() {
        $this->listeners = array();
        return $this;
    }

    protected function notify() {
        foreach ($this->listeners as $listener) {
            $listener->notify();
        }
    }
}

interface IListener 
{
    public function notify();
}

class FirstListener implements IListener 
{
    public function notify() {
        echo "FirstListener::notify() executed<br/>";
    }
}

class SecondListener implements IListener 
{
    public function notify() {
        echo "SecondListener::notify() executed<br/>";
    }
}

class ConcreteObserver 
{
    use Notifier;

    public function __construct() {
        $this
                ->addListener(new FirstListener())
                ->addListener(new SecondListener());
    }

    public function doSomething() {
        echo "start!<br/>";
        $this->notify();
    }

    public function clear() {
        $this->clearListeners();
    }
}

$observer = new ConcreteObserver();
$observer->doSomething();
$observer->clear();
$observer->doSomething();

Результат выполнения кода:

start!
FirstListener::notify() executed
SecondListener::notify() executed
start!

Способ 3

Одним из самых полезных свойств трейтов является то, что класс может использовать их неограниченное количество. Это свойство довольно легко использовать. Например, если вдруг в коде присутствует класс, состоящий из большого количества строк. Раз уж не получается декомпозировать такой класс на более маленькие детали - его функциональнсть можно распихать по нескольким трейтам. Так хотя бы упростится навигация по коду. Таким классом может быть класс api, например.


Полезные источники информации по трейтам

Официальна статья на php.net
Нюансы использония трейтов


Итог

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

P.S.

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

Теги:  ООП  php  traits  php7 

Поделиться статьей

Оставить комментарий