Надеюсь материалы опубликованные в данной статье не покажутся слишком очевидными или покушением на оскорбление чувств «верующих», и уж тем более никто не воспримет название статьи слишком буквально как «ориентированное программирование». В любом случае данная статья это попытка поделиться личным опытом, а не пропаганда своей точки зрения.
Немного предыстории: я человек консервативный, все изменения я принимаю «в штыки», но если преимущества очевидны, то охладив пыл я стараюсь свыкнуться с «новшеством». Так было и с ООП когда в процессе освоения PHP я с ним столкнулся. Сперва я воспринял классы и создание объектов как чистый оверхэд (и не сказать что это было ошибкой), но в процессе знакомства та «структурированность» и «порядок», которые давало ООП сделали из меня религиозного фанатика ООП (и MVC, но сейчас не об этом).
Примерно в то же время я сменил работу и попал в команду разработчиков где многофункциональный портал компании был написан на чистом PHP (без использования фреймворков), и каждый воротил как может. Безусловно там были разработчики высокого уровня, но времени на рефакторинг существующего кода давали очень мало, основное время уходило на расширение функционала, и тем более речи быть не могло об изменении структуры и о том чтобы переписать все с нуля. Там были и примеры «плохого ООП», например в клиентском коде (некоторое подобие контроллера, если проводить аналогию с MVC) в самом начале создавались абсолютно все объекты которые будут задействованы в коде (еще и предварительно инклудились, без какой либо надежды на автолоадер), и что немаловажно, к ним применялось некоторое подобие шаблона делегирования: создавался объект mysqli и присваивался свойству sql каждого объекта. Можно только представить сколько лишних действий в пустую и какой перерасход оперативной памяти происходит.
Вот мой базовый пример:
class A{
public function __construct(){
}
public function doSomething(){
}
public static function someStatic(){
}
}
class B{
public function __construct(){
}
public function doSomething(){
}
public static function someStatic(){
}
}
echo((memory_get_usage()/1024)."Kb"); //результат: 211.90625Kb
Эти замеры только чтобы оценить вес «пустой страницы», то есть тут мы еще не создали ни один объект и не воспользовались ни одним методом из класса.
Теперь давайте посмотрим чего нам будет стоить создание таких «бесполезных» объектов и вызова их методов:
$a=new A;
$a->doSomething();
$b=new B;
$b->doSomething();
echo((memory_get_usage()/1024)."Kb"); //результат: 214.15625Kb
Как видим разница не впечатляющая, но это при условии что и объект и метод по сути ничего не делают вообще. Как известно, в PHP есть статические методы и свойства которые принадлежат классу, в отличие от обычных методов и свойств которые принадлежат объекту данного класса. Наши классы содержат как раз такие методы (совпадение? — не думаю), функционал у них точно такой же как и у обычных методов этого класса, поэтому фактически мы можем сделать те же действия минуя создание класса:
A::someStatic();
B::someStatic();
echo((memory_get_usage()/1024)."Kb"); //результат: 212.6171875Kb
Наверно сравнение на «сверхмалых» числах не дает полной картины, но это тот самый оверхэд от которого можно отказаться. Я не люблю создавать объекты, и именно об этом эта статья, я стараюсь по максимуму использовать статические методы и свойства. Причина не любви к работе с объектом (при передаче другому методу), в том что у меня есть фобия что он не тот за кого себя выдает, конечно Вы можете проверить его на принадлежность к тому или иному классу через instanceof, но чтобы не было непредвиденных результатов лучше проверить его содержимое, что может оказаться муторной работой, и проще собирать нужную структуру по ходу исполнения скрипта (дальше прошу отнестись с пониманием) используя преимущества структурированной системы классов, но не создавая объектов в клиентском коде, по сути в псевдопроцедурном стиле. Но вернемся к коду.
Если Вас волнует вопрос выполнения функционала конструктора, то это реализуется через self:
class A{
public $prop;
public function __construct(){
$this->prop='test';
}
public function doSomething(){
}
public static function someStatic(){
return new self();
}
}
$a=new A();
echo $a->prop; //test
echo((memory_get_usage()/1024)."Kb"); //результат: 211.9140625Kb
echo A::someStatic()->prop; //test
echo((memory_get_usage()/1024)."Kb"); //результат: 211.5703125Kb
Самые внимательные и находчивые конечно предложат, что то типа (и будут правы):
echo ((new A())->prop); //test
echo((memory_get_usage()/1024)."Kb"); //результат: 211.53125Kb
Но это лишь из-за того что примеры исполнения слишком утрированы, а какой-то реализации в подобной схеме можно добиться лишь если метод возвращает этот объект ($this).
Весь этот ход мыслей только для того чтобы объяснить что не обязательно создавать объекты в ООП, и можно обходиться без них экономя память (как в принципе без любых лишних присвоений), если конечно это не оправдано архитектурой приложения, или паттерном, и конечно не стоит даваться в крайности, если Вам нужен объект — создайте объект, иногда без этого не обойтись. Но если результатом каких то действий является объект, то почему бы сразу не начать с ним работать?
Комментарии (64)
alex6636
18.02.2018 12:54+2Сравнение на свехмалых числах не только не даёт полной картины, но и при следующем запуске результат может оказаться противоположным
garex
18.02.2018 13:26+4Конкретно по вашему кейсу gogle for: "Lazy Load", "DI containers".
А вообще судить по набору говнокода о чём бы то ни было — мягко говоря странно.
AlexLeonov
18.02.2018 14:49+6Поток несвязного сознания, имхо.
Читаем в коде:
echo A::someStatic()->prop;
и тут же, ниже:
не обязательно создавать объекты в ООП, и можно обходиться без них экономя память
Вы серьезно верите в то, что в вашем примере объект не создается?
Я вас огорчу, пожалуй. Создается, занимает память, требует лишнего времени на возврат из локального контекста метода в вышележащий контекст (в отличие от конструктора) и будет в память торчать, пока не придет к вам GC.
То, что вы суслика не видите — не значит, что его нет!
P.S. Статья, конечно же, не уровня хабра…
VolCh
18.02.2018 15:28+1Активное использование статических методов класса мало чем отличается от использования обычных функций, оперирующих глобальными переменными, со всеми вытекающими. Разве что чуть лучше инкапсуляция и автозагрузка прикручивается в PHP.
AlexLeonov
18.02.2018 17:12Вы немного преувеличиваете «масштабы бедствия». Статика наследуется, у статики есть LSB, есть области видимости (это вы отметили) и автозагрузка (тоже отметили, очень важно), не забывайте еще про пространства имён.
Не надо считать, что мол «статический метод — всегда зло». Вы же в курсе, что в PHP нет по-настоящему динамических методов, они все по факту статические?Fesor
18.02.2018 17:17не забывайте еще про пространства имён.
а у функций с этим какие-то проблемы?
«статический метод — всегда зло»
никто никогда такого и не говорил. Разве что те, кто не понимают разницу между статическими методами и глобальным стэйтом. Если статика не имеет сайд эффектов — то все хорошо.
AlexLeonov
18.02.2018 17:26а у функций с этим какие-то проблемы?
Да, нет их автозагрузки.Fesor
18.02.2018 17:35автозагрузка это да, и то значение автозагрузки как по мне сильно преувеличено в этом контексте. Я конкретно про функции и пространства имен.
VolCh
18.02.2018 21:43Толку от наследования, если нет нормального способа передать «инстанс» класса-наследника параметром как «инстанс» базового. Или вернуть, присвоить переменой и т. п. Классы в PHP не сущности первого класса в отличии от объектов.
Да, у них есть применение в текущей ситуации, но в большинстве случаев оно приносит больше проблем чем решает.AlexLeonov
18.02.2018 23:05Или вернуть, присвоить переменой и т. п.
$className = getSomeClassName(); $className::someMethod; // или getSomeClassName()::someMethod();
Да, не сами классы не сущности первого класса. Но это легко обходится с помощью использования в качестве сущности имени класса.Fesor
18.02.2018 23:20Но это легко обходится с помощью использования в качестве сущности имени класса.
Завязка на имя класса это в целом очень и очень жесткое ограничение. Вы не находите?
AlexLeonov
19.02.2018 03:35Нахожу, но отношусь философски — таков уж PHP.
Fesor
19.02.2018 11:40Поясните свою философию, будьте добры. Ибо мне, например, непонятно и я могу сделать неправильные выводы из фразы "таков уж PHP". Например "и так сойдет".
AlexLeonov
19.02.2018 17:42Это язык. Сложившийся. Который давно на рынке. И меня нет среди его разработчиков.
Как бы он ни был плох, он занимает около 90% мирового рынка веб-разработки и немалую долю в других нишах.
Можно бесконечно хейтить. А можно изучить и пользоваться.
Я за второй вариант.
А вы?Free_ze
19.02.2018 18:40и немалую долю в других нишах
Это в какой нише, кроме веба, PHP имеет немалую долю?AlexLeonov
19.02.2018 23:44Мне последние годы доводится заниматься именно не-веб-PHP.
В основном это различная потоковая обработка данных, нормализация, загрузка из в БД, агрегация в БД и построение различных отчетных данных.
Сплошной cli.Fesor
20.02.2018 11:22Мм… а что тогда web php? Просто то что вы перечислили — это обычное дело в контексте web разработки.
AlexLeonov
20.02.2018 12:00Так нет у этих проектов веб-морд. Какая же это веб-разработка?
Fesor
20.02.2018 13:29следуя этой логике любой проект под мобилки это не web (что в целом правильно).
Или же, если у меня есть морда, которая представляет из себя простенький дашборд, а под копотом десяток демонов которые занимаются обработкой данных — это уже не web или уже web?
Я больше о том что делить web и не web если по итогу делается одно и то же (работа с базой данных, обработка данных)?
Вот если бы вы сказали что под десктопы на php пишите — тут да, "другие ниши".
AlexLeonov
20.02.2018 21:43Под десктоп пока не писал. А вот проектов без интерфейса — было довольно много…
VolCh
20.02.2018 12:23"web php" принимает на вход прежде всего всякие $_GET, $_POST и т. п.
не "web php"их игнорит
:)
VolCh
20.02.2018 14:09+1Почему PHP выбран для cli? Основная причина, когда могут на этот вопрос ответить что-то кроме "а больше я ничего не знаю на достаточном уровне", это "на 90% кодовая база пересекается".
AlexLeonov
20.02.2018 21:43Нет, не всё так просто. Выбран в первую очередь из соображений производительности, как бы для вас это странно ни звучало. И уже во вторую очередь — из-за скорости разработки.
Fesor
19.02.2018 23:26Я за второй вариант.
А вы?Я за разумное использование доступных фич. То есть если что-то можно сделать в языке, это далеко не означает что эту комбинацию фич стоит использовать в новом коде.
Напомню, что вопрос мой был относительно описанных вами примеров, завязке на имена классов и в целом повсеместное использование статики.
он занимает около 90% мирового рынка веб-разработки
80% из которой можно вообще в расчет не брать.
VolCh
19.02.2018 12:09+1У PHP есть такие возможности, относящиеся скорее к "хакам", чем к "лучшим практикам", но из этого не следует, что нужно на них строить архитектуру приложения в целом.
AlexLeonov
19.02.2018 17:41Из чего не нужно строить архитектуру? Из того, что имя класса — это string? Или нельзя использовать константу SomeClass::class?
Будьте добры поясните, что вы считаете хаком, недопустимым к использованию в архитектуре?
СпасибоVolCh
19.02.2018 19:25Построение архитектуры на основе "хаков", использующих динамическое задание имени класса в строковых переменных, сильно усложняет сопровождение и развитие системы с такой архитектурой. Как минимум, за счёт наличия множества зависимостей, которые гораздо сложнее обнаружить при анализе.
Как локальное решение, например, в DI-контейнере, выдающим по SomeInterface::class инстанс реализации — вполне. Как основной способ передачи ссылок на данные — очень усложняет понимание.
VolCh
19.02.2018 02:10Теряя многие возможности, включая автодополнения и рефакторинг в IDE тайп-хинтинга в рантайме, статанализ и т. п. Я же говорю, приносит проблем больше чем решает обычно :)
AlexLeonov
19.02.2018 03:36PHPDoc решает все проблемы.
SerafimArts
19.02.2018 04:54Ну, скажем, не все. С дженерикоподобными типами вы ничего не поделаете, разве что юнион прописать, но там сложная история…
Fesor
19.02.2018 11:43Опишите мне интерфейс для функции через phpdoc. А потом попробуйте описать дженерики для коллекций/промисов/etc. Спойлер — у вас не получится.
VolCh
19.02.2018 12:06Какой тег PhpDoc документирует переменную, параметр или результат как содержащую имя класса?
Какой тег PhpDoc проводит проверку в рантаймк параметра или результата на содержание имени класса, унаследованного от базового?
Fesor
19.02.2018 12:41не смотря на то что я с вами согласен, относительно
Какой тег PhpDoc проводит проверку в рантаймк параметра
AOP + Design By Contract покрывает все эти вещи и даже больше.
VolCh
19.02.2018 13:33Не спорю, но это требует как наличие "продвинутой" культуры разработки, так и "продвинутого" инструментария, по сравнению с "мэйнстримовыми" "лучшими практиками". Читай: "это более дорого в текущих реалиях рынка".
SerafimArts
19.02.2018 14:40Это не работает с кодогенерацией. Т.е. кусок симфони и почти вся доктрина идут лесом.
VolCh
19.02.2018 16:08В какой-то мере ровно наоборот. Те же роуты в виде аннотаций в классах контроллера относятся скорее к AOP, чем к классическим парадигмам.
SerafimArts
20.02.2018 00:57Говоря о конкретных багах и проблемах я имел ввиду уже существующие и более классические реализации AOP: github.com/goaop
Fesor
18.02.2018 17:21+2Налицо явное непонимание идеи ООП.
но это тот самый оверхэд от которого можно отказаться.
если вас парит факт оверхэда на создание объектов — почему бы не отказаться от пересоздания объектов на каждый запрос? Вот это было бы разумно и реально давало бы профит. А так — это просто стыд.
Fesor
18.02.2018 17:41+1не обязательно создавать объекты в ООП
вот только это не ООП а структурное программирование в лучшем случае. Идея ООП в том, что у вас не просто есть объекты, а в том что они взаимодействуют между собой, что есть late binding. Что до оверхэда — если данные не будут покидать приделов объектов, и если вы выкидываете из уравнения модель выполнения "обработай и умри" — то оверхэд будет как раз таки не в вашей реализации.
то есть банальный пример. Продемонстрируйте мне как будет выглядеть late biding в контексте статических методов? Скажем, такой простой шаблон как декоратор реализуйте.
Lure_of_Chaos
19.02.2018 08:40Негодую и соболезную.
Мне, как и многим моим коллегам, приходилось сталкиваться с ситуацией, когда расширение и поддержка существующего кода — чистый ад, а возможности все это отрефакторить к такой-то матери — нет, гонят новыми фичами. И что обидно — джуники и матерые рукожопы здесь на коне, потому что за 5 минут могут навалять косо-криво работающее решение, а шишки будешь получать ты, опытный разработчик, потому что уже полдня возишься и не можешь исправить эту «маленькую» но неприятную ошибку, исправление которой поднимает на поверхность все новые и новые косяки давно отошедших от проекта коллег…
В таком случае я рекомендую заняться рефакторингом втихую, чуть понизив свою производительность, при этом чуть пожертвовав своей свободой. понемногу, не спеша, умело расставляя такие «костыли», которые в нужный момент превратятся в более соответствующую архитектуру. Вряд ли внутренний перфекционист будет доволен поностью, но самые вопиющие недостатки, влияющие на ясность кода и производительность, будут сглажены.
Поверьте, таким образом уже был «спасен» не один проект.ToshiruWang
19.02.2018 09:29Согласен, за рефакторинг такого лучше не приниматься — захочется убивать людей.
Код друзей таких авторов
BaseClass, ClassA : BaseClass, ClassB : BaseClass, ...
ClassA a;
ClassB b;
ClassC c;
if(a != null)
{
a->someMethod();
} // Даже без else, хотя инициализируется только одна переменная и группы
if(b != null)
{
b-> someMethod();
}
Тоже не понимают что такое ООП, потому как делали только простые программки с парой функций, а что такое сложная система даже не представляют.
PS. Это, конечно, не без создания объектов, но проблемы там примерно те же.
index0h
20.02.2018 02:11создавался объект mysqli и присваивался свойству sql каждого объекта. Можно только представить сколько лишних действий в пустую и какой перерасход оперативной памяти происходит.
Да, ссылки нынче дорогие(((
Наверно сравнение на «сверхмалых» числах не дает полной картины
И смысла не имеет, если на то пошло
но это тот самый оверхэд от которого можно отказаться.
Это экономия на спичках
Причина не любви к работе с объектом (при передаче другому методу), в том что у меня есть фобия что он не тот за кого себя выдает
Type hinting появился еще в 5.1
чтобы не было непредвиденных результатов лучше проверить его содержимое, что может оказаться муторной работой
Как же инкапсуляция?
Я не спорю, бывают ситуации, когда статика оправдана, например методы стандартной библиотеки, и то далеко не всегда. Но память — это вообще не тот случай. То, что парни из вашей работы не смогли в lazy load — это печально.
Но, в большинстве случаев статика приводит к:
- Бесконтрольной связности и хрупкости проекта
Если вы меняете статический метод — вы меняете его для всей системы сразу, этого нельзя сделать конкретно для этого сервиса и для следующего, на переписав их за компанию.
- Экспоненциальному росту сложности тестирования
Ваши тесты будут на прямую зависеть от реализации статических методов и вы их не сможете подменить.
- Статика с состоянием — это пичалька
Вы просто должны верить, что она как-то работает и ниоткуда она не будет вызвана не правильно, или не в той последовательности.
ElectroGuard
20.02.2018 11:47Стоит всё таки разобраться в ООП прежде чем его хаять. Касается, впрочем, не только ООП.
kruslan
Зря вы так. Не буду ничего говорить про тесты, но сам подход постоянного использования статиков несет очень много боли в поддержке и рефакторинге.
AlexLeonov
Нет беды в статических методах, поверьте. Тут беда в полном непонимании как PHP, так и программирования в целом…
kruslan
Верю. Использую. Но только тогда, когда это действительно полезно или, хотя-бы, принесет минимум проблем в будущем. В реальности — хелперы, которые должны быть функциями)
AlexLeonov
Вы излишне категоричны. Есть много вещей, которые действительно принадлежат классу объектов, но не конкретному объекту.
Статику надо уметь готовить ))
Fesor
А приведите примеры. Причем лично меня интересуют именно те примеры, которые обладают состоянием.
AlexLeonov
Вам чтобы поспорить? Или действительно интересно?
Ну ОК, состав первичного ключа (список полей, входящих в него) — это свойство* класса моделей, а не конкретной модели. Не нужно, как в Ларавеле, создавать объект-модель, чтобы просто узнать, какой у нее ПК.
* «свойство» здесь вполне может быть публичным методом, возвращающим разный результат в зависимости от get_called_class(), например
kruslan
Эмм… Не хочу спорить, но пытаюсь понять, чем тут поможет статик-метод…
Правильно я понимаю, что для получения состава PK необходимо сделать запрос в базу и получить информацию по определенной таблице (которая, возможно, лежит в каком-то методе или свойстве — их тоже статиком делать из-за вашего сценария)? И подобный запрос (с кешированием) делается в ларавеле при создании модели? Тогда почему не воспользоваться этим (что и делает ларавел)?
А что если модель может получать данные из нескольких разных таблиц, в которых разные PK. Какая именно таблица выбирается по внешним факторам. В этом случае проще и правильнее именно создавать объект модели, а не эмулировать такое поведение.
Если я прав — статический метод не нужен, скорее вреден.
VolCh
Тут, скорее всего, о получении состава ключа из данных маппинга ActiveRecord, который в некоторых реализациях "зашивается" в данные уровня объектов, в некоторых в данные класса, а в некоторых вообще базируется на соглашениях типа "имя класса равно имени таблицы".
AlexLeonov
Нет, запрос не делается.
Просто я хочу знать, какое поле (поля) образуют первичный ключ. Это может быть константа, может быть какая-то функция, которая этот состав мне сообщит.
Неважно.
Важно то, что состав первичного ключа — свойство не объекта, но класса. Если допустить обратное, получается абсурд — каждая запись может иметь разные по составу первичные ключи?
kruslan
Ну тогда проще и правильнее константу, не? Ну а если вам нужен именно какой-то метод — правильнее вынести его в хелпер и вызывать с передачей в него параметров, от которых зависит результат. Собственно, я про такое использование и говорил выше ;)
На самом деле — нет. По вашему примеру — состав ключа вообще не относится к моделям.
VolCh
Это свойство не столько класса, сколько метаданных объекта. Их можно получать из объекта, из класса, откуда-то ещё.
Fesor
а я называю это метаданными. И предпочитаю что бы метаданные модели жили отдельно от самой модели. Более того, я предпочитаю иметь объект, описывающий метаданные и другой объект мне их предоставляющий.