Nano Hash - криптовалюты, майнинг, программирование

Черты в PHP — какие-нибудь реальные примеры/лучшие практики?

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

Тем не менее, я до сих пор не знаю, как я буду использовать трейты в своих проектах.

Существуют ли проекты с открытым исходным кодом, в которых уже используются трейты? Любые хорошие статьи/материалы для чтения о том, как структурировать архитектуру с использованием трейтов?

25.10.2011

  • Вот мое мнение: сообщение в блоге по этому вопросу что я писал по теме. TL;DR: По сути, я боюсь, что, хотя они мощные и могут быть использованы во благо, большинство применений, которые мы увидим, будут полными антипаттернами и причинят гораздо больше боли, чем решат... 25.10.2011
  • Взгляните на стандартную библиотеку scala, и вы узнаете много полезных примеров признаков. 03.10.2012

Ответы:


1

Мое личное мнение состоит в том, что на самом деле очень мало применения трейтов при написании чистого кода.

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

class ClassName {
    protected $logger;

    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
    // or
    public function setLogger(LoggerInterface $logger) {
        $this->logger = $logger;
    }
}

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

25.10.2011
  • Используя трейты, вы также можете использовать другой класс регистратора, верно? Просто отредактируйте трейт, и все классы, использующие этот трейт, будут обновлены. Поправьте меня если я ошибаюсь 25.10.2011
  • @rickchristie Конечно, вы могли бы это сделать. Но вам нужно будет отредактировать исходный код черты. Таким образом, вы должны изменить его для каждого класса, использующего его, а не только для конкретного, для которого вам нужен другой регистратор. А что, если вы хотите использовать один и тот же класс, но с двумя разными регистраторами? Или если вы хотите пройти mock-logger во время тестирования? Вы не можете, если вы используете трейты, вы можете, если вы используете внедрение зависимостей. 25.10.2011
  • Я понимаю вашу точку зрения, я также размышляю, стоят ли черты того или нет. Я имею в виду, что в современных фреймворках, таких как Symfony 2, у вас повсюду есть внедрение зависимостей, что в большинстве случаев кажется лучше, чем трейты. На данный момент я вижу черты не намного больше, чем копирование и вставка с помощью компилятора. ;) 25.10.2011
  • На данный момент я считаю трейты не чем иным, как копированием и вставкой с помощью компилятора. ;): @Max: Это именно то, для чего были созданы трейты, так что это совершенно правильно. Это делает его более удобным для сопровождения, так как есть только одно определение, но в основном это просто c&p... 25.10.2011
  • @ircmaxell и NikiC — что также приходит мне на ум: мой предпочтительный способ внедрения зависимостей в класс — это DI-контейнер, как в упомянутом фреймворке Smyfony 2. Но в некоторых ситуациях DI-контейнер недоступен/не может быть реализован, например, унаследованные кодовые базы. Может быть, трейты могли бы помочь в качестве поддерживаемой языком функции, упрощающей повторное использование горизонтального кода? 26.10.2011
  • @Max: Я бы сказал, что унаследованная кодовая база, о которой вы говорите, выиграет больше от рефакторинга, чтобы иметь возможность использовать DI (и, следовательно, быть тестируемой и получить все преимущества, которые она приносит), чем от использования черт там ... 26.10.2011
  • @ircmaxell это, вероятно, правда, всегда зависит от того, насколько велик рефакторинг. Я имею в виду, эл. г. это не всегда возможно, может быть, из-за нехватки времени, поэтому, если меня будут давить, я лучше добавлю трейт вместо того, чтобы копировать еще немного кода, а затем, как вы правильно упомянули, исправлю его на следующей итерации. Просто как идея, где черты могут быть полезны. 26.10.2011
  • NikiC упускает суть: использование трейта не мешает использовать внедрение зависимостей. В этом случае трейт просто позволит каждому классу, реализующему ведение журнала, не дублировать метод setLogger() и создание свойства $logger. Черта обеспечит их. setLogger() будет вводить подсказку в LoggerInterface, как в примере, так что любой тип регистратора может быть передан. Эта идея аналогична ответу Гордона ниже (только похоже, что он намекает тип на суперкласс Logger, а не на интерфейс Logger ). 07.10.2012
  • Слишком часто архитектура, которую люди предлагают, является правильной. Архитектура настолько сложна, что многие не могут понять ее, и это приносит больше вреда, чем пользы. Внедрение зависимостей и черты могут использоваться для аналогичных целей, но каждый из них оптимизирован для разных вариантов использования. Использование DI, когда Traits является более подходящим, приводит к гораздо более сложной и, следовательно, трудной в использовании иерархии классов. 10.10.2013
  • @NikiC Это просто показывает, что ты вообще не разбираешься в чертах. Трейты — это единственный способ, по крайней мере, в PHP, вводить многократно используемый код в классы с разными предками. Например, повторное использование одних и тех же методов голосования в классах Post и Comment. Это альтернативный подход к получению множественного наследования на языке, который его не поддерживает. Я активно использую черты. 03.10.2014
  • @noun: Я думаю, вы не знаете, кто такой nikiC... подсказка: проверьте участников репозитория php-src на github: вам не нужно много прокручивать, он один из ведущих участников. Насколько вы знаете, он даже написал часть реализации трейта. И, учитывая, как часто вы используете черты: вот запись в блоге о почему не стоит. C++ поддерживает множественное наследование, но многие разработчики скажут вам, что вам не следует идти по этому пути, да, черты существуют, делает ли это их хорошими? Нет 07.06.2015
  • @Элиас Правда? Ипсе Диксит! Поскольку NikiC является автором базового кода PHP, его мнение актуально, а мое нет? Это действительно смешно. Он просто неправ. Чтобы решить все проблемы, которые вы описали в своей статье, просто используйте интерфейс и реализуйте трейт сам интерфейс. Подсказка типа сохраняется, как и контракт. Если вы измените трейт, а новая реализация не удовлетворит интерфейс, lint выделит ошибки, потому что контракт заключается между интерфейсом и классом, который его реализовал. Трейт — это просто фрагмент кода, введенный интерпретатором. 16.06.2015
  • @существительное: я не говорю, что твое мнение не имеет значения. Я просто указываю на то, что ваше заявление о том, что NikiC не понимает черты, довольно абсурдно, учитывая, что он, вероятно, знает о них больше, чем вы и я вместе взятые. Что я скажу, однако, так это то, что вы, кажется, не понимаете, что я пытался сделать в своем блоге. Sure Trait + Interface решает большинство проблем, о которых я говорю. Однако: интерфейсы являются только общедоступными методами. Конфликты имен трейтов все еще существуют, и самое главное: я еще не сталкивался с проблемой, которая требовала бы от меня использования трейтов. 16.06.2015
  • Моя основная проблема с трейтами заключается просто в следующем: то, как реализованы трейты, оставляет слишком много места для ошибки оператора. Есть множество PHP-разработчиков, которые не понимают таких вещей, как принцип лискова, или SRP, или DI. То, как вы теперь можете злоупотреблять трейтами, не поможет представить PHP как глупый язык, который плохо построен и не подходит для серьезного программирования. 16.06.2015
  • @noun Для протокола: я не думаю, что трейты совершенно бесполезны, просто у них относительно мало приложений, которые я бы посчитал совместимыми с хорошо разработанным кодом. Черты — это, по сути, копирование и вставка кода с помощью компилятора, и не так много случаев, когда копирование и вставка кода действительно является предпочтительным решением проблемы. Но, конечно, есть некоторые, обычно из-за ограничений других языков (в частности, здесь множественное наследование). 16.06.2015
  • @NikiC: подразумевают ли черты, являющиеся по сути механизмом c&p с помощью компилятора, что отключение кеша OPCode может привести к увеличению дискового ввода-вывода? а вы случайно не знаете, обоснованы ли мои подозрения насчет трейтов, реализованных аналогично абстрактным классам? Благодарность 17.06.2015
  • Внедрение зависимостей — это пример создания экземпляра объекта (например, каждый экземпляр имеет свои собственные конкретные зависимости). Черты — это пример композиции класса. Вы составляете сам класс, чтобы все экземпляры имели эти черты. Если бы для этого вы использовали внедрение зависимостей, вы бы повторяли код при каждом создании экземпляра. 17.08.2015
  • Трейты решают проблему множественного наследования, которой нет в PHP. 25.10.2016

  • 2

    Я предполагаю, что нужно некоторое время изучать языки, которые имеют черты, чтобы изучить общепринятые хорошие/лучшие практики. Мое текущее мнение о Trait заключается в том, что вы должны использовать их только для кода, который вам придется дублировать в других классах с той же функциональностью.

    Пример трейта Logger:

    interface Logger
    {
        public function log($message, $level);    
    }
    
    class DemoLogger implements Logger
    {
        public function log($message, $level)
        {
            echo "Logged message: $message with level $level", PHP_EOL; 
        }
    }
    
    trait Loggable // implements Logger
    {
        protected $logger;
        public function setLogger(Logger $logger)
        {
            $this->logger = $logger;
        }
        public function log($message, $level)
        {
            $this->logger->log($message, $level);
        }
    }
    
    class Foo implements Logger
    {
        use Loggable;
    }
    

    А затем вы это делаете (демонстрация)

    $foo = new Foo;
    $foo->setLogger(new DemoLogger);
    $foo->log('It works', 1);
    

    Я полагаю, что важно учитывать при использовании трейтов то, что они на самом деле являются просто фрагментами кода, которые копируются в класс. Это может легко привести к конфликтам, например, когда вы пытаетесь изменить видимость методов, например.

    trait T {
        protected function foo() {}
    }
    class A { 
        public function foo() {}
    }
    class B extends A
    {
        use T;
    }
    

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

    trait T {
        public function foo() {
        return 1;
    }
    }
    class A { 
        use T;
        public function foo() {
        return 2;
    }
    }
    
    $a = new A;
    echo $a->foo();
    

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

    class A
    {
        use T;
        protected $prop = 1;
        protected function getProp() {
            return $this->prop;
        }
    }
    
    trait T
    {
        public function foo()
        {
            return $this->getProp();
        }
    }
    
    $a = new A;
    echo $a->foo();
    

    работает (демонстрация), но теперь черта тесно связана с A, и вся идея горизонтального повторного использования потерял.

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

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

    Примечание: трейты — это новая концепция PHP, поэтому все мнения, высказанные выше, могут быть изменены. У меня еще не было много времени, чтобы оценить концепцию самостоятельно. Но я надеюсь, что этого достаточно, чтобы дать вам пищу для размышлений.

    25.10.2011
  • Это интересный вариант использования: используйте интерфейс, который определяет контракт, используйте трейт, чтобы удовлетворить этот контракт. Неплохо. 25.10.2011
  • Мне нравятся такие настоящие программисты, которые предлагают реальные рабочие примеры с кратким описанием для каждого. Спасибо 11.10.2012
  • Что, если кто-то вместо этого использует абстрактный класс? Заменив интерфейс и трейт, можно создать абстрактный класс. Кроме того, если интерфейс так необходим для приложения, абстрактный класс также может реализовать интерфейс и определить методы, как это сделал trait. Итак, вы можете объяснить, почему нам все еще нужны черты? 16.01.2013
  • @sumanchalki Абстрактный класс следует правилам наследования. Что, если вам нужен класс, реализующий Loggable и Cacheable? Вам понадобится класс для расширения AbstractLogger, который затем должен расширить AbstractCache. Но это означает, что все Loggables являются кэшами. Это соединение, которое вам не нужно. Это ограничивает повторное использование и портит ваш граф наследования. 16.01.2013
  • мне интересно, как demoLogger связывается с Logger :( 09.12.2013
  • @Red В приведенном выше примере demoLogger не полагается на какой-либо внешний класс для ведения журнала. Он просто повторяет сообщение при входе в систему. Как объяснил @Gordon, его собственная функция log() не будет перезаписана используемой чертой. Поэтому я думаю, что это просто должно служить примером класса, реализующего интерфейс Logger без использования трейтов. 04.03.2014
  • Я думаю, что демонстрационные ссылки мертвы 14.03.2016
  • хорошо, я собираюсь проголосовать за другой ваш случайный ответ только потому, что он заслуживает как минимум +20: это лучший способ, который я нашел, чтобы, наконец, избежать всего шаблонного кода для доступа к методам компонентов из композитный, например $foo->getLogger()->log(). Теперь я могу делать $foo->log() и в то же время продолжать использовать композицию. Здорово ! 24.07.2016

  • 3

    :) Я не люблю теоретизировать и спорить о том, что с чем-то делать. В данном случае черт. Я покажу вам, для чего я нахожу черты полезными, и вы можете либо извлечь из этого уроки, либо проигнорировать их.

    Черты — они отлично подходят для применения стратегий. Короче говоря, шаблоны проектирования стратегии полезны, когда вы хотите, чтобы одни и те же данные обрабатывались (фильтровались, сортировались и т. д.) по-разному.

    Например, у вас есть список продуктов, которые вы хотите отфильтровать по каким-то критериям (бренды, характеристики и т. д.) или отсортировать по разным признакам (цена, этикетка и т. д.). Вы можете создать трейт сортировки, содержащий разные функции для разных типов сортировки (числовых, строковых, дат и т. д.). Затем вы можете использовать эту черту не только в своем классе продуктов (как показано в примере), но и в других классах, которым нужны аналогичные стратегии (для применения числовой сортировки к некоторым данным и т. д.).

    Попробуй:

    <?php
    trait SortStrategy {
        private $sort_field = null;
        private function string_asc($item1, $item2) {
            return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
        }
        private function string_desc($item1, $item2) {
            return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
        }
        private function num_asc($item1, $item2) {
            if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
            return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
        }
        private function num_desc($item1, $item2) {
            if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
            return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
        }
        private function date_asc($item1, $item2) {
            $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
            $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
            if ($date1 == $date2) return 0;
            return ($date1 < $date2 ? -1 : 1 );
        }
        private function date_desc($item1, $item2) {
            $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
            $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
            if ($date1 == $date2) return 0;
            return ($date1 > $date2 ? -1 : 1 );
        }
    }
    
    class Product {
        public $data = array();
    
        use SortStrategy;
    
        public function get() {
            // do something to get the data, for this ex. I just included an array
            $this->data = array(
                101222 => array('label' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'),
                101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'),
                101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'),
                101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'),
                101219 => array('label' => 'Meh product', 'price' => 3.69, 'date_added' => '2012-06-11'),
            );
        }
    
        public function sort_by($by = 'price', $type = 'asc') {
            if (!preg_match('/^(asc|desc)$/', $type)) $type = 'asc';
            switch ($by) {
                case 'name':
                    $this->sort_field = 'label';
                    uasort($this->data, array('Product', 'string_'.$type));
                break;
                case 'date':
                    $this->sort_field = 'date_added';
                    uasort($this->data, array('Product', 'date_'.$type));
                break;
                default:
                    $this->sort_field = 'price';
                    uasort($this->data, array('Product', 'num_'.$type));
            }
        }
    }
    
    $product = new Product();
    $product->get();
    $product->sort_by('name');
    echo '<pre>'.print_r($product->data, true).'</pre>';
    ?>
    

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

    01.11.2012
  • В то время как это поддерживает чистоту общедоступного интерфейса, внутренний может стать очень сложным из-за этого, особенно если вы распространите это на другие вещи, например, на цвета. Я думаю, что здесь лучше использовать простые функции или статические методы. 14.01.2016
  • Мне нравится термин strategies. 17.09.2018

  • 4

    Я в восторге от Traits, потому что они решают общую проблему при разработке расширений. для платформы электронной коммерции Magento. Проблема возникает, когда расширения добавляют функциональность базовому классу (например, модели User), расширяя его. Это делается путем указания автозагрузчику Zend (через файл конфигурации XML) использовать модель пользователя из расширения, и эта новая модель расширяет базовую модель. (пример) Но что, если два расширения переопределяют одну и ту же модель? Вы получаете «состояние гонки», и загружается только один.

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

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

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

    TL;DR Я думаю, что Traits могут быть полезны для создания расширений/модулей/плагинов для больших программных пакетов PHP, таких как Magento.

    19.01.2013

    5

    У вас может быть черта для объекта только для чтения, например:

      trait ReadOnly{  
          protected $readonly = false;
    
          public function setReadonly($value){ $this->readonly = (bool)$value; }
          public function getReadonly($value){ return $this->readonly; }
      }
    

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

    23.04.2013
  • Таким образом, класс, который будет use использовать эту черту, затем вызовет if($this -> getReadonly($value)); но это вызовет ошибку, если вы не use эту черту. Поэтому этот пример некорректен. 23.05.2013
  • Ну, вам нужно сначала проверить, используется ли черта. Если черта ReadOnly определена для объекта, вы можете проверить, доступен ли он только для чтения или нет. 22.07.2013
  • Я сделал общее доказательство концепции такой черты в gist.github.com/gooh/4960073 04.09.2013
  • Вы должны объявить интерфейс для ReadOnly для этой цели 19.01.2017
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..