All streams
Search
Write a publication
Pull to refresh

Comments 22

Два тривиальных примера, не имеющие к ООП никакого отношения: описание классов абсолютно побоку. То, что аргументы в функции лучше не изменять — известный тезис, который к функциональному программированию не привязан. Собственно, всю вашу статью можно ужать до этого тезиса.
По поводу тривиальности примеров — имхо, но примеры должны быть тривиальными по своей сути поскольку их задачей является упрощение усвоения информации, а не наоборот.
По поводу отношения к ООП — пожалуйста, уточните, потому что есть мнение, что оба приведенных примера соответствуют парадигме ООП по всем трем пунктам.
Изменение аргументов функций — речь здесь не об этом, а о том, что функции (в данном случае приватные методы) не зависят от внутреннего состояния объекта (его полей, если совсем конкретно) и не изменяют его.
>но примеры должны быть тривиальными
Да сама идея тривиальная, формулируется в одном предложении, из разряда прописных истин.

>По поводу отношения к ООП
То, что функции желательно не менять состояние параметров — к ООП не имеет никакого отношения.

>функций… не зависят от внутреннего состояния объекта
О том и речь. Функции, не зависящие от состояния объекта вообще не являются методами, и непонятно зачем они вообще методы объекта, а не класса. Зачем вообще тогда класс приведен — не ясно.
>но примеры должны быть тривиальными
Опыт показывает, что эта прописная истина все же ускальзывает от некоторых разработчиков, применяющих разделение власти и часто рефакторинг заканчивается разбиением большого метода на несколько маленьких, каждый из которых получает доступ к состоянию объекта. Что, во многом, равносильно добавления комментариев в код.
>По поводу отношения к ООП
Рассмотрено сочетание парадигм ООП и функционального программирования, отсюда название.
>функций… не зависят от внутреннего состояния объекта
«О том и речь. Функции, не зависящие от состояния объекта вообще не являются методами, и непонятно зачем они вообще методы объекта, а не класса. Зачем вообще тогда класс приведен — не ясно.»
Вы правы, их можно отнести к методам класса. Класс нужен для примеров, в обоих случаях модифицируется состояние объекта этого класса — в первом в приватных методах, во втором — в паблик.
Эванс в книге про DDD так и писал: избегайте методов с побочными эффектами (В его определении — методы, меняющие внутреннее состояние объекта). Как раз оно
И да, первый листинг… грубо говоря не очень удачен.
Уверен, что 99% процентов мыслей, которые возникнут в моей голове уже кто-то успел подумать и возможно даже изложить в письменном виде и опубликовать.
«И да, первый листинг… грубо говоря не очень удачен.» — спасибо, так и планировалось.
А зачем так и планировалось? Заходя в топик, я думал вы хотите показать преимущества функционального подхода и вариант «как надо», а вы в очередной раз показали «как не надо».
Все правильно, первый вариант — как не надо.
Вариант возникновения первого примера — результат «неполного» рефакторинга метода по принципу разделения власти.
Второй вариант — как надо.
Преимущества функционального подхода были описаны в разделе «Плюсы».
Кстати, речь не только об изменении методами состояния объекта (побочных эффектах), но и о том, что приватные методы (хелперы) не должны обращаться к состоянию объекта сами — все свои параметры они получают в качестве аргументов.
Хотя такой принцип и не претендует на универсальность, его, в первую очередь, стоит применять в случаях, когда можно четко сформулировать задачу метода-хелпера (и, соответственно, назвать его).
А зачем эти методы делать методами, тем более методами объекта, а не класса (static)?

Если уж на это пошло, то методы должны быть обычными функциями, нет?
Ну это тогда совсем функциональщина будет, а мы же фанаты ООП как никак
Не совсем. Я постоянно практикую обращение из паблик методов к функциями, хотя бы стандартной библиотеки, а часто создаю и свои, в том числе и реализующих бизнес-логику, если она прямо к объекту мало привязана или повторяется в объектах, которые сложно в одну иерархию ввести. Условно эти функции можно назвать хелперами, в ООП модель их сложно вписать с разумным обоснованием, потому и не вписываю, а оставляю просто функциями.
А если объединить эти методы в traits (PHP 5.4) подключать к тем или иным «базовым» классам — как считаете?
Смысла не вижу. Всё таки в состав класса надо вводить функции, у которых хотя бы один параметр $this непосредственно. Пока очевидным предназначением traits мне видится реализация интерфейсов типа Iterable По сути traits+интерфейсы дают обычное множественное наследование, не без нюансов, но всё же.
Другой случай: а если этим функциям (просто функциям) нужен доступ к DI конетйнеру (например, в контейнере может лежать сервис доступа к базе). Вы будете одним из параметров передавать контейнер в функцию при каждом вызове?

Или вы объединили бы все такие функции в виде методов в trait, чтобы иметь возможность получать сервисы через $this->container->…?
Да, если это просто функции. А вот с traits я слабо представляю как это реализовать. Ведь контейнер надо инициализировать, а это чаще всего в конструкторе происходит. Но в конструкторе не только типовые инициализации, чтобы и конструктор в traits засунуть.
На мой взгляд разбиение не только можно, но и нужно. Вы правильно подметили сходство с функциональным подходом, что можно писать приватные методы, не изменяющие состояние объекта. В итоге метод, породивший вызовы приватных (добавил бы от себя вспомогательных или «дочерних») методов, принимает или отвергает «окончательное решение об изменении состояния». Таким образом данный прием позволяет локализовать точку изменения состояния объекта. Если во время выполнения приватного метода произойдет ошибка: let it crash… Важно также, чтобы «главный метод» не менял пошагово состояние объекта, а делал это атомарно в конце. Такое-же разбиение можно делать не только для публичных методов, но и везде, где уместно, включая дробление приватных методов. Такую программу легче отлаживать и повторно использовать. В особенности если есть интерфейсы.

Проблемы могут возникнуть при накоплении «вычисляемого состояния» — усложнение входных/выходных параметров в приватных методах. Если методов много — затрудняет отладку. Легко увлечься программированием ради программирования. Гораздо важнее сосредоточиться не на коде, а на данных, структурах и взаимоотношениях между ними. Если это будет правильно, код сам ляжет как надо )))… постепенно… может быть… )))

Еще один интересный эффект данного подхода: все «приватные методы» могут быть static, впрочем об этом уже говорилось в комментариях выше.

К данному подходу можно добавить еще простые правила: код метода должен максимально умещаться на одной условной странице экрана редактора и не только по высоте ))) Если это не так: требуется разбиение либо что-то явно неправильно. Любой метод должен быть заточен на выполнение одной функциональности: «do one job»
В первом листинге в _experience помещаются непосредственно результаты опытов над выбранными людьми:

_experience.push(_experimentProcedure.do(human));

, а во втором в _experiencе помещается другой объект типа Experience, который возвращается из метода experiment().

_experience.push(experiment(_experimentProcedure, _chosenOnes));

То есть, эти две функции не эквивалентны (если я правильно понял код =)
Эти два примера эквивалентны по результату своей работы, но во втором листинге функция experiment не обращается к полям объекта Invasion и не редактирует их. Контроль над состоянием объекта находится у функции investage, она же решает, какие поля нужно передать в experiment. Кроме того, в первом листинге метод investigate несет намного меньше смысловой нагрузки, по сравнению с методами updateChosenOnesList и experiment (что на мой взгляд так же является признаком плохого кода). Во втором листинге этот недостаток также исправлен.
Я имею ввиду, нет ли во втором случае лишней операции push?
Не, во втором листинге все верно =) здесь подразумевается, что объект Experience умеет обрабатывать push как результата выполнения ExperimentProcedure, так и другого такого же объекта (результат второй операции — добавление всего опыта из передаваемого объекта Experience). Т.е. в функции experiment создается и наполняется объект Experience, который затем добавляется в поле _experience.
Sign up to leave a comment.

Articles