0,0
рейтинг
23 марта 2013 в 15:13

Разработка → Шаблон программирования «Текучий интерфейс» в PHP. Свежий взгляд


При разработке программного обеспечения одной из важных составляющих является высокая читабельность исходного кода программы. Существуют специальные методики и рекомендации, которые позволяют добиться улучшения читабельности исходного кода. Одной из методик улучшения читабельности исходного кода является применение «текучих интерфейсов» (англ. Fluent Interface). О нем мы и поговорим в данной статье.


Эволюция. От простого к сложному.

Могу предположить, что каждый программист начинает свой путь PHP-программиста с написания банального приложения «Hello, world!». После которого будут идти долгие года изучения языка и неуклюжие попытки сделать что-то особенное: ORM/CMS/Framework (нужное подчеркнуть). Думаю, у всех есть тот код, который лучше никому не показывать. Но это абсолютно нормальный процесс развития, потому что без понимания простых вещей нельзя разобраться в сложных! Поэтому, давайте, повторим этот путь — начнем с простых примеров и дойдем до реализации «текучего» интерфейса в виде отдельного класса с помощью АОП. Те, кто знает этот шаблон программирования в ООП — могут смело переходить к последней части статьи, там можно получить отличную пищу для размышлений.

Приступим-с

Чтобы нам не пришлось ходить далеко за примером, давайте возьмем некоторую сущность пользователя, у которого есть свойства имени, фамилии, а также пароля:

class User
{
    public $name;
    public $surname;
    public $password;
}


Превосходный класс, который можно легко и изящно использовать:

$user = new User;

$user->name     = 'John';
$user->surname  = 'Doe';
$user->password = 'root';


Однако легко заметить, что у нас нет никакой валидации и можно сделать пароль пустым, что не очень хорошо. Помимо этого, было бы неплохо знать, что значения полей не изменяются без нашего ведома (Immutable). Эти несколько соображений приводят нас к мысли о том, что свойства должны быть защищенными или приватными, а доступ к ним осуществлять через пару геттер/сеттер. (примечание: этот подход как раз и лежит в основе прокси-классов Doctrine)

Сказано-сделано:

class User
{
    protected $name;
    protected $surname;
    protected $password;

    public function setName($name)
    {
        $this->name = $name;
    }

    public function setSurname($surname)
    {
        $this->surname = $surname;
    }

    public function setPassword($password)
    {
        if (!$password) {
            throw new InvalidArgumentException("Password shouldn't be empty");
        }
        $this->password = $password;
    }
}


Для нового класса конфигурация немного изменилась и теперь использует вызов методов-сеттеров:

$user = new User;

$user->setName('John');
$user->setSurname('Doe');
$user->setPassword('root');



Вроде, ничего сложного, ведь так? А что если нам надо настроить 20 свойств? 30 свойств? Этот код будет засыпан вызовами сеттеров и постоянным появлением $user-> Если же имя переменной будет $superImportantUser, то читаемость кода ухудшится еще больше. Что же можно предпринять, чтобы избавиться от копирования этого кода?

Текучий интерфейс

Итак, мы подошли к шаблону программирования Fluent Interface, который был придуман Эриком Эвансом и Мартином Фаулером для повышения читабельности исходного кода программы за счет упрощения множественных вызовов методов одного объекта. Реализуется это с помощью цепочки методов (Method Chaining), передающих контекст вызова следующему методу в цепочке. Контекстом является значение, возвращаемое методом и этим значением может быть любой объект, включая текущий.

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

class User
{
    protected $name;
    protected $surname;
    protected $password;

    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }

    public function setSurname($surname)
    {
        $this->surname = $surname;
        return $this;
    }

    public function setPassword($password)
    {
        if (!$password) {
            throw new InvalidArgumentException("Password shouldn't be empty");
        }
        $this->password = $password;
        return $this;
    }
}


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

$user = new User;
$user->setName('John')->setSurname('Doe')->setPassword('root');


Как вы уже заметили, конфигурация объекта теперь занимает меньше места и читается значительно легче. Мы достигли поставленной цели! В этом месте у многих разработчиков должен возникнуть вопрос: «И что? Я это и так знаю...» Тогда попробуйте ответить на вопрос: «Чем плох текучий интерфейс в данном виде?» перед чтением следующего блока статьи.

Так чем же он плох?

Наверное, вы не нашли ответа и решили его прочитать? ) Ну тогда вперед! Спешу вас успокоить: на текущем уровне ООП с текучим интерфейсом все хорошо. Однако если подумать, то можно заметить, что его нельзя реализовать в виде отдельного класса и подключить к нужному объекту. Эта особенность выражается в том, что приходится монотонно проставлять return $this в конце каждого метода. Если же у нас пара десятков классов с парой десятков методов, которые мы желаем сделать «текучими», то приходится вручную заниматься этой неприятной операцией. Это и есть классическая сквозная функциональность.

Давайте наконец-то сделаем его с помощью отдельного класса

Так как у нас сквозная функциональность, то нужно подняться на уровень выше ООП и описать этот паттерн формально. Описание получается весьма простое — при вызове публичных методов в некотором классе необходимо возвращать в качестве результата метода сам объект. Чтобы не получить неожиданных эффектов — давайте сделаем уточнения: публичные методы должны быть сеттерами (начинаются на set) и классы будем брать только те, которые реализуют интерфейс-маркер FluentInterface.
Конечное описание «текучего» интерфейса в нашей реализации на PHP будет звучать так: при вызове публичных методов-сеттеров, начинающихся на set, и находящихся в классе, реализующем интерфейс FluentInterface — необходимо возвращать в качестве результата вызова метода сам объект, для которого осуществляется вызов, при условии что оригинальный метод ничего не вернул. Вот как! Теперь осталось дело за малым — опишем это с помощью кода АОП и библиотеки Go! AOP:

Первым делом, опишем интерфейс-маркер «текучего» интерфейса:

/**
 * Fluent interface marker
 */
interface FluentInterface
{

}


А дальше сама логика «текучего» интерфейса в виде совета внутри аспекта:

use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Around;

class FluentInterfaceAspect implements Aspect
{
    /**
     * Fluent interface advice
     *
     * @Around("within(FluentInterface+) && execution(public **->set*(*))")
     *
     * @param MethodInvocation $invocation
     * @return mixed|null|object
     */
    protected function aroundMethodExecution(MethodInvocation $invocation)
    {
        $result = $invocation->proceed();
        return $result!==null ? $result : $invocation->getThis();
    }
}


Сделаю небольшое пояснение — совет Around задает хук «вокруг» оригинального метода класса, полностью отвечая за то, будет ли он вызван и какой результат будет возвращен. Это будет со стороны выглядеть так, как будто мы взяли код метода и немного изменили его код, добавив туда наш совет. В самом коде совета мы сперва вызываем оригинальный метод сеттера и если он ничего не вернул нам, то возвращаем в качестве результата вызова оригинального метода сам объект $invocation->getThis(). Вот такая вот незатейливая реализация этого полезного шаблона программирования всего в пару строчек.

После всего этого, подключение «текучего» интерфейса в каждый конкретный класс приложения — простая и приятная работа:

class User implements FluentInterface
{
    //...
    public function setName($name)
    {
        $this->name = $name;
    }    
}


Все, что нам нужно, чтобы использовать теперь текучий интерфейс в конкретном классе — просто добавить интерфейс — implements FluentInterface. Никакого копирования return $this по сотням методов, только чистый исходный код, понятный маркер интерфейса и сама реализация «текучего» интерфейса в виде простого класса аспекта. Всю работу возьмет на себя АОП.

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

Ссылки:
  1. Go! Aspect-Oriented Framework for PHP
  2. Wikipedia — Fluent Interface
  3. Github project
Ваше мнение относительно реализации текучего интерфейса с помощью АОП

Проголосовало 783 человека. Воздержалось 238 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Лисаченко Александр @NightTiger
карма
45,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (67)

  • +5
    Напишите пожалуйста что-нибудь про примеры применения и отладки АОП. Меня сильно останавливает возможность АОП случайно обвесить не те классы а потом выяснять почему всё сломалось.
    • +2
      Это будет отличная тема )
      • +1
        Было бы неплохо ещё вкратце описать, как реализуется АОП в PHP — это фича новой версии PHP, расширение, препроцессор или что-то другое?
        • +3
          Реализуется АОП в PHP ручками )
          В самом PHP это пока не планируется, хотя для версии 4.0 были попытки, я даже где-то видел грамматику лексера PHP с учетом аспектов, но это уже в прошлом. Из нового — есть PECL-расширение PHP-AOP, которое разрабатывается, но там куча багов, сегфолтов, да и скорость в отдельных случаях меньше, чем у моей PHP-библиотеки. Моя же библиотека написана на чистом PHP и не требует никаких дополнительных экзотических расширений, за счет этого она стабильна и активно развивается.
          Идея работы такая — отслеживаем момент загрузки любого PHP-кода (перехватываются incude/require), делаем статический анализ кода, вносим в него необходимые правки, создаем декоратор, подменяем им оригинальный класс и сохраняем в кэше, дальше отдаем этот код парсеру PHP. После таких манипуляций в памяти PHP будет находиться класс, уже имеющий расширенную функциональность, но с прежним именем класса.
          Это краткий обзор предыдущих серий Санта-Барбары под названием «как сделать АОП»
          • 0
            (перехватываются incude/require)
            Вроде из предыдущего поста я понял, что перехватывается autoload, то на функцию в include аспект не повесить прозрачно.
            • 0
              ок, уточнение: я перехватываю именно require/include и как частный случай, получается, перехватываю autoload, потому что где-то в нем тоже есть include/require. Сама функция include/require для меня недосягаема, но вот путь к подключаемому файлу я изменить могу, что моя библиотека и делает.
              • 0
                Не понял.

                Если в index.php первой строчкой у меня require 'superlib.php'; то могу я аспектами функцию из это йлибы перехватить или нет?
                • 0
                  нет, не сможете, это под силу только расширению, такому как runkit, либо PHP-AOP. Тот код, который уже загружен в память, невозможно изменить со стороны PHP. Но вот если вы перед этим подключите АОП, а потом уже передадите управление на superlib.php, то методы в классах уже можно будет перехватить.
                  • 0
                    Это-то понятно, что сначала нужно подключить. Я о том, что можно ли аспектировать именно функции (для которых нет автолоада), а не методы в классах?
                    • +1
                      Это будет позже, вся теория и практические наброски у меня готовы. Можно будет перехватывать даже простые функции, например file_get_contens, mysqli_open и т.д. Но только если они будут использоваться в неймспейсе, а не в глобальном пространстве.
              • 0
                Каким образом вы изменяете путь к подключаемому файлу, если не секрет? Из ваших (очень запутанных) исходников я так и не смог этого понять…
                • +3
                  Регистрируется фильтр потока php://filter/read, который передаёт тело файла цепочке трансформеров, среди которых есть FilterInjectorTransformer, заменяющий пути в аргументах, передаваемых include* и require*.

                  Насчёт запутанности исходников: там как раз всё хорошо структурировано, а по-настоящему запутанные исходники — они во всяких wordpress.
                  • 0
                    Ну то есть не перехватывается include/require :), эти директивы изменяются их только в тех файлах, которые уже распарсены. А парсятся файлы путем подмены autoload.

                    В общем, прикольно, конечно…
                    • +1
                      Меня формулировка автора тоже заинтриговала — полез в исходники, в надежде увидеть особую уличную магию. Разочарован не был — академическая ценность библиотеки достаточно высокая.
                      • +4
                        Да, там есть несколько мест, которые приводили меня в полный ступор, потому что не было возможности двинуться дальше из-за всяких ограничений языка. Два года потребовалось на то, чтобы решить этот большой ребус, но я рад, что у меня все получилось и я могу показать ее сообществу.
                        Надеюсь, она найдет свое место в приложениях, потому что область применения широка: контрактное программирование, логирование, кэширование, новые шаблоны программирования, новые подходы к разработке ПО.
                        • 0
                          Отличная работа. Жду новые эксперименты.
  • 0
    Эта статья как раз один из примеров управляемого «АОП»: вы в любой момент можете посмотреть на класс и понять, что он использует реализацию FluentInterface-а. А потом можно зайти в сам интерфейс FluentInterface, нажать «иерархия» и получить отчет о всех классах, которые его реализуют.
    • 0
      А можете подробнее рассказать про ваше последнее предложение? Как получить отчет о всех классах, которые используют этот интерфейс? Куда нажать и где найти «иерархия»?
      • +1
        Все очень просто (если у вас phpStorm): зайдите в нужный вам класс и на самом классе нажмите Ctrl+H. Я думал, что этот инструмент всем знаком. )
        • 0
          Ах PHPStorm, вы же об этом не упомянули в комментарии выше, спасибо.
        • +1
          Добавлю, что в Eclipse — Ctrl + T
  • +1
    «Fluent Interface» в данном случае, скорее, «связный (цепочечный, контекстносвязанный) синтаксис», по аналогии с «fluent English». хотя в словарях такого перевода нет. К примеру, было
    Все, что нам нужно, чтобы использовать теперь текучий интерфейс в конкретном классе — просто добавить интерфейс — implements FluentInterface.
    стало
    Всё, что нам нужно, чтобы получить связный синтаксис в конкретном классе — просто добавить интерфейс — implements FluentInterface.

    Вот, в Википедии написано:
    Текучий интерфейс (англ. fluent interface, название придумано Эриком Эвансом и Мартином Фаулером)...
    Но они-то англичане, им совершенно понятна аналогия между fluent English и Fluent Interface. А почему мы должны топорно калькировать их идиомы? Да, в некоторых языках метод реализации иногда совпадает с названием Interface, как в PHP и С#. Но для остальных языков никакой подсказки нет: видим использование контекстносвязанного синтаксиса, который называют «текучим интерфейсом» (?).

    Но из плюсов — теперь ваша статья может служить источником этого утверждения в Википедии:: )
    Такой стиль косвенно полезен повышением наглядности и интуитивности кода [источник не указан 692 дня].
    • 0
      Тоже долго думал над тем, как лучше перевести на русский язык этот шаблон, но так и не нашел корректного перевода, поэтому остановился на «текучем», хоть это название меня и раздражает. Я бы его переименовал в ChainInterface и тогда бы он прекрасно сочетался с MethodChaining и переводился бы просто «цепочечным интерфейсом».
      Приходится отдавать дань дядюшке Фаулеру )
    • 0
      Если вы не знали, в Википедии (по крайней мере русской) англичан, как вы выразились, не так уж и много.
  • +2
    эм. вы правда предлагаете сделать обертку вокруг всех методов в и так не сомом быстром скриптовом языке?
    • 0
      Я предлагаю это сделать только для публичных сеттеров, оверхед при их использовании будет настолько мал, что вы даже его не увидите на общем фоне приложения. Вряд ли у вас в коде сеттеры вызываются несколько тысяч раз за запрос, ведь так?
      • +1
        Ну, вполне возможен вариант работы с 30к объектами в пределах одной сессии скрипта + инициализации каждого (объекта) десяткой сеттеров.
        С другой стороны, может такие приложения не на PHP нужно писать (1), а также даже этот оверхед будет очень мал по сравнению с логикой работы приложения, которое оперирует таким количеством объектов (2).
  • +11
    1. Вижу большое количество проблем при автокомплите в IDE. На данный момент PHPStorm не всегда корректно справляется с обычными методами, унаследованными от другого класса и возвращающими $this, а в такой обертке про автокомплит можно забыть.
    А без автокомплита я слабо представляю себе продуктивную работу.

    2. Проблема написания большого количества геттеров и сеттеров решается либо средствами IDE, либо с помощью кодогенерации.
    К примеру, у меня есть схема БД, одной консольной командой по ней генерируется базовый класс со всеми геттерами и сеттерами. Потом он наследуется «основным» и в нем уже вся реализация логики приложения.
    При изменении схемы базовый класс автоматически перегенерируется, не затрагивая логику.
    • +2
      Спасибо за ваш комментарий, очень правильные замечания.
      1. На текущий момент автокомплит для таких методов работать точно не будет, что очень неудобно. Но ваш комментарий подтолкнул меня к идее и я только что нашел способ обойти и эту проблему: надо позволить аспектам работать и с док-блоками. В этом случае в phpDoc-блоках можно будет с помощью совета добавить return self и в этом случае phpStorm радостно включает автокомплит на основе полученного класса в кэше.
      2. Если у вас есть инструмент кодогенерации для Doctrine — это хорошо, но вот нет у меня уверенности, что весь vendor-код оборудован такими полезными утилитами. Да и ваш пулл-реквест в ядро большого фреймворка с проставленными сеттерами могут и не взять. А вот АОП это легко сделает, потому что это одна из полезных особенностей АОП — возможность дотягиваться до любого кода, внося в него свои правки на уровне приложения.
      • +3
        Если мне не изменяет память, return self, как и return static в phpStorm нормально так пока еще и не заработал.
        • 0
          Это уже задача разработчиков IDE — научиться понимать такие конструкции нормально. Потому что мы код документируем корректно.
          Хотя у меня, вроде, и так все хорошо (phpStorm 127.100): построил сейчас цепочку из 10 методов с конструкцией @return self — все подхватывает правильно.
          • –1
            Хорошо, но не всё. При наследовании «новый» класс не подхватывается.

            • 0
              Хм, проверил static. Видимо таки сделали корректно работающими оба свойства:

            • 0
              @inheritdoc
            • +4
              Так тут все правильно же, из b() возвращается Test, в котором f() нет.
      • +1
        Значит для каждого метода надо будет написать аннотацию с @ return, причем явно указать какой класс возвращается.
        Соотв. автокомплит для унаследованных классов будет ущербный. Как по мне, лучше и понятнее написать return $this в теле метода, чем в аннотации, и не городить магию.

        Инструмент кодогенерации не доктриновский, но аналогичный, да. Не очень понял зачем мне делать pull-request в ядро большого (?) фреймворка, если я работаю на уровне приложения и все геттеры\сеттеры относятся к реализации моей модели.
        • 0
          Согласен, что реализация FluentInterface на АОП — не самая лучшая затея хотя бы с точки зрения логичности: наличие конструкции implements SomeInterface предполагает то, что класс будет содержать код реализующий интерфейс, чего при этом подходе нет.
          • +2
            Это концепция маркеров, вы с ней не сталкивались ранее? Когда, например, класс исключения помечают дополнительно такими маркерами, а потом ловят по классу интерфейса-маркера.

            class AppException extends Exception implements ServiceLayerException
            {
            }
            
            // ...
            
            try {
                throw new AppException();
            } catch (ServiceLayerException $e) {
                // .. service layer fault
            }
            
        • +2
          Не обязательно вам писать аннотацию с @return, ее может вставить сама библиотека АОП, а IDE ее подхватит сама. Насчет магии — это ваш выбор, использовать или нет. Статья имеет исключительно ознакомительный характер, но ваши мысли и опасения мне понятны.

          Не всегда сеттеры находятся в моделях. И не всегда они ваши и находятся на уровне приложения. Очень часто я встречаю код, где нужно настроить вендорский сервис, а в нем есть лишь тонна сеттеров и нет цепочечного интерфейса.
          • +2
            Очень часто я встречаю код, где нужно настроить вендорский сервис, а в нем есть лишь тонна сеттеров и нет цепочечного интерфейса.

            Основной плюс АОП для меня это именно возможность адаптировать сторонний софт без ручного написания однотипных прокси и адаптеров
  • +3
    А вся «магия», связывающая аспект с реализациями интерфейса, находится в комментарии к методу:

    @Around("within(FluentInterface+) && execution(public **->set*(*))")
    

    Очень надеялся увидеть в статье побольше информации про этот магический синтаксис, но даже упоминания не нашел.
    • +4
      Автор просто рассчитывает, что читатель знаком с его предыдущими топиками.
    • +1
      Да, как вы уже поняли, это синтаксис, описывающий точки во всем коде, аналог SQL для данных. Синтаксис пришел из аннотаций Spring и AspectJ, поэтому с ними лучше ознакомиться отдельно. Если есть интерес — могу в отдельной статье рассмотреть этот синтаксис и как он применяется относительно к PHP. Хотя сейчас Go! поддерживает довольно небольшое количество срезов из того, что предлагает AspectJ.
  • 0
    зачем усложнять?
  • +1
    при вызове публичных методов-сеттеров, начинающихся на set, и находящихся в классе, реализующем интерфейс FluentInterface — необходимо возвращать в качестве результата вызова метода сам объект, для которого осуществляется вызов, если метод ничего не возвращаетт

    fixed

    Интересное применение АОП, но вот не уверен, что нужно анализировать возврат сеттера. Сеттер по идее ничего возвращать не должен, процедура, а не функция. А если возвращает, то NULL может быть валидным значением.
    • 0
      Спасибо за уточнение, внес его в статью.
      Насчет анализа возвращаемого значения — это делать необходимо, иначе можно случайно сломать существующий метод, который возвращает другое значение (например, экземпляр другого класса). Без проверки результата совет подменит результат и вернет его вместо оригинального без зазрения совести )
      • 0
        Для сеттеров довольно необычное поведение вообще что-то возвращать. А если явно возвращает NULL, то это будет штатное поведение и заменять его на this нельзя. По идее нужно анализировать код метода на предмет наличия в нем return: если есть, то нельзя чэйнить, если есть — нельзя.
    • 0
      Насколько я помню в PHP нет полноценного типа void, как следствие метод (с том числе и без return) всегда будет возращать null.
  • +2
    Слишком затратно для экономии одной строчки в коде метода.

    Как демонстрация подхода — да, годно.
    • 0
      Как говорят: «копейка — рубль бережет». Так и здесь, в коде одного метода — да, всего одна строчка, в коде класса их может быть уже пара десятков, а в коде всего приложения — несколько тысяч.
      А еще интереснее будет, если у вас уже есть эти несколько тысяч сеттеров по всему проекту и у вас есть необходимость сделать их цепочечными, чтобы было удобно работать.
      • +1
        С несколькими тысячами и производительность просядет уже заметно.

        Вот если бы эти все аспектные дела оформить в pecl extension… :)
  • +3
    А где в голосовании вариант «Статья понравилась, но все слишком непрозрачно и непонятно, буду делать ручками по-старинке.»? :)
    • 0
      А этот вариант не подошел: «Статья понравилась, возьму на заметку, но пока не время для АОП, остановлюсь на ООП.»?
      • 0
        Нет, моя мысль была в том, что автору за его труд и описание шаблона определённо респект, т.к. я не исключаю, что есть люди, которые увидев предложенный метод, напишут лучший / более красивый / etc код, чем был до этого. В любом случае, каким бы шаблон ни был, он улучшает читаемость кода. Но моё личное мнение, что именно так, как описано в статье делать не надо.
        Выше уже дали комментарии с предложением альтернатив, с некоторыми из которых я согласен.
  • –1
    По-моему мнению такой исходный код легче читается:

    a->method1('some text1');
    a->method2('some text2');
    a->method3('some text3');
    b->method1('some text1');
    b->method2('some text2');
    c->method1('some text1');
    c->method2('some text2');
    b->method_c( c );
    a->method_b( b );

    чем

    a->method1('some text1')->method2('some text2')->method3('some text3')->method_b(
    b->method1('some text2')->method2('some text2')->method_c(
    c->method1('some text1')->method2('some text2')
    )
    );
    • +6
      А как-то так?
      aaaaa
          ->method1('some text1')
          ->method2('some text2')
          ->method3('some text3')
      ;
      bbbbbb
          ->method1('some text1')
          ->method2('some text2')
      ;
      ccccc
          ->method1('some text1')
          ->method2('some text2')
      ;
      bbbbb->method_c( c );
      aaaaa->method_b( b );
      
      • –3
        Ну у вас тут представлен красиво отформатированный код, который редко встречается в моей повседневной жизни, зачастую попадается только злоупотребление данным методом, доводящим до маразма:

        aaaaa->method1(editText1->getText())->method2(substr(editText2->getText(), 0, 10))->method3(preg_match('/.*sometext.*/', substr(editText3->getText(), 10)))->method_b(bbbbbb->method1('some text1')->method2('some text2')->method_c(ccccc->method1(superpuperclass->getMeltaPupper()->hello()->checkIfexist())->method2(someClass->getSomemethod())));

        p.s. тэги source — php, не работают
        • 0
          Вроде все приличные IDE позволяют если не полностью красиво отформатировать код всего проекта, то очень близко к нему. И конструкции типа preg_match('/.*sometext.*/', substr(editText3->getText(), 10)) выносить в переменную практически на автомате.
        • +1
          красиво отформатированный код, который редко встречается в моей повседневной жизни

          Если единственное, что вас смущает во fluent-интерфейсе это то, что ваши коллеги не умеют им пользоваться, то проблема решается на другом уровне. Научите их пользоваться автоформаттером (в любой нормальной IDE он есть), воспользуйтесь каким-нибудь StyleCop для проверки на сервере, или просто бейте по рукам :)
  • +1
    Отсутствие автокомплита, проблемы с дебагом и всё ради экономии на одной строчке?

    Да и само по себе разыменование этакий антипаттерн, указывающий на надобность вынесения последовательности во внутренний метод. Плюс проблемы с дебагом и логированием и тестированием.

    p.s. У вас пример неудачный, а вот сама библиотека для AOP хороша
  • 0
    По мне так лучше был бы with как в Delphi.
    with $objectUser do {
      method1('1');
      method2('2');
      method3('3');
    }
    

    описание на то как это происходит в Delphi
    • +1
      Это как в Pascal :) А вообще, да, иногда не хватает такой конструкции, особено в форме with $this
      • 0
        Вам в JavaScript этой конструкции мало :)? По-моему, эта конструкция удобна лишь в небольшом количестве случаев, а в остальных только добавляет путаницы. Например:

        with $objectUser do {
            method1(strtolower($var));
            method2(method1('2'));
            method3('3')
        }
        


        Что должно произойти на первой строчке? Должна ли выполниться функция strtolower() или нужно искать метод strtolower() в объекте $objectUser?
        На второй? А если существует глобальная функция method1()?
        • 0
          Поиск нужной реализации идет по восходящей от текущей области видимости до глобальной. Нормальное, логичное, ожидаемое поведение.
  • 0
    такая банальность, а сколько пафоса.
    чем плох интерфейс? отладкой.

  • 0
    Обсуждение топика закончилось через 2 дня после его публикации… Но я всё же задам вопрос автору: Есть где-нибудь данные о возрастании стоимости исполнения кода, я имею ввиду на сколько милисекунд увеличится время исполнения кода. Введение дополнительных обработчиков не может быть бесплатным.
    • 0
      Есть данные с прошлой версии фреймворка на слабенькой машинке: performance test. Этот тест показывает, что 10000 итераций пустого метода выполняются за 3.0мс, тогда как с дополнительным советом в моей библиотеке — за 90мс. Если делать аналогичное с помощью экстеншена PHP-AOP, то получится за 25мс. Однако, скорость экстеншена очень сильно падает с ростом количества советов (обратите внимание, как начинает тормозить экстеншен, в случае если есть много срезов, которые не совпадают с измеряемым методом)
      Тем не менее, это синтетический тест, потому что, как правило, сам метод работает в реальных условиях медленнее совета, вызывается всего раз или два и замедление скорости от совета практически незаметно из-за значительной оптимизации. Таким образом, добавление совета к большому методу почти не тормозит его работу и если этот метод вызывается один или несколько раз, то его даже не будет заметно.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.