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


Немного предыстории: я человек консервативный, все изменения я принимаю «в штыки», но если преимущества очевидны, то охладив пыл я стараюсь свыкнуться с «новшеством». Так было и с ООП когда в процессе освоения 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)


  1. kruslan
    18.02.2018 12:42
    +3

    Зря вы так. Не буду ничего говорить про тесты, но сам подход постоянного использования статиков несет очень много боли в поддержке и рефакторинге.


    1. AlexLeonov
      18.02.2018 14:50
      +1

      Нет беды в статических методах, поверьте. Тут беда в полном непонимании как PHP, так и программирования в целом…


      1. kruslan
        18.02.2018 20:40

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


        1. AlexLeonov
          19.02.2018 03:38

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

          Статику надо уметь готовить ))


          1. Fesor
            19.02.2018 11:39

            А приведите примеры. Причем лично меня интересуют именно те примеры, которые обладают состоянием.


            1. AlexLeonov
              19.02.2018 17:46

              Вам чтобы поспорить? Или действительно интересно?

              Ну ОК, состав первичного ключа (список полей, входящих в него) — это свойство* класса моделей, а не конкретной модели. Не нужно, как в Ларавеле, создавать объект-модель, чтобы просто узнать, какой у нее ПК.

              * «свойство» здесь вполне может быть публичным методом, возвращающим разный результат в зависимости от get_called_class(), например


              1. kruslan
                19.02.2018 18:21

                Эмм… Не хочу спорить, но пытаюсь понять, чем тут поможет статик-метод…
                Правильно я понимаю, что для получения состава PK необходимо сделать запрос в базу и получить информацию по определенной таблице (которая, возможно, лежит в каком-то методе или свойстве — их тоже статиком делать из-за вашего сценария)? И подобный запрос (с кешированием) делается в ларавеле при создании модели? Тогда почему не воспользоваться этим (что и делает ларавел)?

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

                Если я прав — статический метод не нужен, скорее вреден.


                1. VolCh
                  19.02.2018 19:34

                  Тут, скорее всего, о получении состава ключа из данных маппинга ActiveRecord, который в некоторых реализациях "зашивается" в данные уровня объектов, в некоторых в данные класса, а в некоторых вообще базируется на соглашениях типа "имя класса равно имени таблицы".


                1. AlexLeonov
                  19.02.2018 23:43

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

                  Неважно.

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


                  1. kruslan
                    20.02.2018 01:25

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

                    Ну тогда проще и правильнее константу, не? Ну а если вам нужен именно какой-то метод — правильнее вынести его в хелпер и вызывать с передачей в него параметров, от которых зависит результат. Собственно, я про такое использование и говорил выше ;)

                    Важно то, что состав первичного ключа — свойство не объекта, но класса.

                    На самом деле — нет. По вашему примеру — состав ключа вообще не относится к моделям.


                  1. VolCh
                    20.02.2018 12:21

                    Это свойство не столько класса, сколько метаданных объекта. Их можно получать из объекта, из класса, откуда-то ещё.


              1. Fesor
                19.02.2018 23:28

                это свойство* класса моделей, а не конкретной модели.

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


  1. alex6636
    18.02.2018 12:54
    +2

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


  1. garex
    18.02.2018 13:26
    +4

    Конкретно по вашему кейсу gogle for: "Lazy Load", "DI containers".


    А вообще судить по набору говнокода о чём бы то ни было — мягко говоря странно.


  1. vitaliy2
    18.02.2018 14:44
    +1

    Я не знаю, как в php, но в js чтобы два объекта заняли 200 КБ, нужно иметь в каждом из них по 25 тыс собственных полей.

    Обычный объект в js занимает 40–80 байт.


    1. VolCh
      18.02.2018 15:28
      +1

      Это не два объекта, а весь рантайм.


  1. AlexLeonov
    18.02.2018 14:49
    +6

    Поток несвязного сознания, имхо.

    Читаем в коде:

    echo A::someStatic()->prop;

    и тут же, ниже:
    не обязательно создавать объекты в ООП, и можно обходиться без них экономя память


    Вы серьезно верите в то, что в вашем примере объект не создается?

    Я вас огорчу, пожалуй. Создается, занимает память, требует лишнего времени на возврат из локального контекста метода в вышележащий контекст (в отличие от конструктора) и будет в память торчать, пока не придет к вам GC.

    То, что вы суслика не видите — не значит, что его нет!

    P.S. Статья, конечно же, не уровня хабра…


  1. VolCh
    18.02.2018 15:28
    +1

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


    1. AlexLeonov
      18.02.2018 17:12

      Вы немного преувеличиваете «масштабы бедствия». Статика наследуется, у статики есть LSB, есть области видимости (это вы отметили) и автозагрузка (тоже отметили, очень важно), не забывайте еще про пространства имён.

      Не надо считать, что мол «статический метод — всегда зло». Вы же в курсе, что в PHP нет по-настоящему динамических методов, они все по факту статические?


      1. Fesor
        18.02.2018 17:17

        не забывайте еще про пространства имён.

        а у функций с этим какие-то проблемы?


        «статический метод — всегда зло»

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


        1. AlexLeonov
          18.02.2018 17:26

          а у функций с этим какие-то проблемы?

          Да, нет их автозагрузки.


          1. Fesor
            18.02.2018 17:35

            автозагрузка это да, и то значение автозагрузки как по мне сильно преувеличено в этом контексте. Я конкретно про функции и пространства имен.


      1. VolCh
        18.02.2018 21:43

        Толку от наследования, если нет нормального способа передать «инстанс» класса-наследника параметром как «инстанс» базового. Или вернуть, присвоить переменой и т. п. Классы в PHP не сущности первого класса в отличии от объектов.

        Да, у них есть применение в текущей ситуации, но в большинстве случаев оно приносит больше проблем чем решает.


        1. AlexLeonov
          18.02.2018 23:05

          Или вернуть, присвоить переменой и т. п.


          $className = getSomeClassName();
          $className::someMethod;
          
          // или
          
          getSomeClassName()::someMethod();
          


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


          1. Fesor
            18.02.2018 23:20

            Но это легко обходится с помощью использования в качестве сущности имени класса.

            Завязка на имя класса это в целом очень и очень жесткое ограничение. Вы не находите?


            1. AlexLeonov
              19.02.2018 03:35

              Нахожу, но отношусь философски — таков уж PHP.


              1. Fesor
                19.02.2018 11:40

                Поясните свою философию, будьте добры. Ибо мне, например, непонятно и я могу сделать неправильные выводы из фразы "таков уж PHP". Например "и так сойдет".


                1. AlexLeonov
                  19.02.2018 17:42

                  Это язык. Сложившийся. Который давно на рынке. И меня нет среди его разработчиков.

                  Как бы он ни был плох, он занимает около 90% мирового рынка веб-разработки и немалую долю в других нишах.

                  Можно бесконечно хейтить. А можно изучить и пользоваться.

                  Я за второй вариант.
                  А вы?


                  1. Free_ze
                    19.02.2018 18:40

                    и немалую долю в других нишах
                    Это в какой нише, кроме веба, PHP имеет немалую долю?


                    1. kruslan
                      19.02.2018 18:50

                      Шел-скрипты?))))


                    1. AlexLeonov
                      19.02.2018 23:44

                      Мне последние годы доводится заниматься именно не-веб-PHP.
                      В основном это различная потоковая обработка данных, нормализация, загрузка из в БД, агрегация в БД и построение различных отчетных данных.
                      Сплошной cli.


                      1. Fesor
                        20.02.2018 11:22

                        Мм… а что тогда web php? Просто то что вы перечислили — это обычное дело в контексте web разработки.


                        1. AlexLeonov
                          20.02.2018 12:00

                          Так нет у этих проектов веб-морд. Какая же это веб-разработка?


                          1. Fesor
                            20.02.2018 13:29

                            следуя этой логике любой проект под мобилки это не web (что в целом правильно).


                            Или же, если у меня есть морда, которая представляет из себя простенький дашборд, а под копотом десяток демонов которые занимаются обработкой данных — это уже не web или уже web?


                            Я больше о том что делить web и не web если по итогу делается одно и то же (работа с базой данных, обработка данных)?


                            Вот если бы вы сказали что под десктопы на php пишите — тут да, "другие ниши".


                            1. AlexLeonov
                              20.02.2018 21:43

                              Под десктоп пока не писал. А вот проектов без интерфейса — было довольно много…


                              1. oxidmod
                                20.02.2018 23:10
                                +1

                                cli — это тоже интерфейс, так то =)


                                1. AlexLeonov
                                  21.02.2018 09:58

                                  Не поспоришь ))


                        1. VolCh
                          20.02.2018 12:23

                          "web php" принимает на вход прежде всего всякие $_GET, $_POST и т. п.
                          не "web php"их игнорит


                          :)


                      1. VolCh
                        20.02.2018 14:09
                        +1

                        Почему PHP выбран для cli? Основная причина, когда могут на этот вопрос ответить что-то кроме "а больше я ничего не знаю на достаточном уровне", это "на 90% кодовая база пересекается".


                        1. AlexLeonov
                          20.02.2018 21:43

                          Нет, не всё так просто. Выбран в первую очередь из соображений производительности, как бы для вас это странно ни звучало. И уже во вторую очередь — из-за скорости разработки.


                  1. Fesor
                    19.02.2018 23:26

                    Я за второй вариант.
                    А вы?

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


                    Напомню, что вопрос мой был относительно описанных вами примеров, завязке на имена классов и в целом повсеместное использование статики.


                    он занимает около 90% мирового рынка веб-разработки

                    80% из которой можно вообще в расчет не брать.


              1. VolCh
                19.02.2018 12:09
                +1

                У PHP есть такие возможности, относящиеся скорее к "хакам", чем к "лучшим практикам", но из этого не следует, что нужно на них строить архитектуру приложения в целом.


                1. AlexLeonov
                  19.02.2018 17:41

                  Из чего не нужно строить архитектуру? Из того, что имя класса — это string? Или нельзя использовать константу SomeClass::class?

                  Будьте добры поясните, что вы считаете хаком, недопустимым к использованию в архитектуре?

                  Спасибо


                  1. VolCh
                    19.02.2018 19:25

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


                    Как локальное решение, например, в DI-контейнере, выдающим по SomeInterface::class инстанс реализации — вполне. Как основной способ передачи ссылок на данные — очень усложняет понимание.


          1. VolCh
            19.02.2018 02:10

            Теряя многие возможности, включая автодополнения и рефакторинг в IDE тайп-хинтинга в рантайме, статанализ и т. п. Я же говорю, приносит проблем больше чем решает обычно :)


            1. AlexLeonov
              19.02.2018 03:36

              PHPDoc решает все проблемы.


              1. SerafimArts
                19.02.2018 04:54

                Ну, скажем, не все. С дженерикоподобными типами вы ничего не поделаете, разве что юнион прописать, но там сложная история…


              1. Fesor
                19.02.2018 11:43

                Опишите мне интерфейс для функции через phpdoc. А потом попробуйте описать дженерики для коллекций/промисов/etc. Спойлер — у вас не получится.


              1. VolCh
                19.02.2018 12:06

                Какой тег PhpDoc документирует переменную, параметр или результат как содержащую имя класса?


                Какой тег PhpDoc проводит проверку в рантаймк параметра или результата на содержание имени класса, унаследованного от базового?


                1. Fesor
                  19.02.2018 12:41

                  не смотря на то что я с вами согласен, относительно


                  Какой тег PhpDoc проводит проверку в рантаймк параметра

                  AOP + Design By Contract покрывает все эти вещи и даже больше.


                  1. VolCh
                    19.02.2018 13:33

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


                  1. SerafimArts
                    19.02.2018 14:40

                    Это не работает с кодогенерацией. Т.е. кусок симфони и почти вся доктрина идут лесом.


                    1. VolCh
                      19.02.2018 16:08

                      В какой-то мере ровно наоборот. Те же роуты в виде аннотаций в классах контроллера относятся скорее к AOP, чем к классическим парадигмам.


                      1. SerafimArts
                        20.02.2018 00:57

                        Говоря о конкретных багах и проблемах я имел ввиду уже существующие и более классические реализации AOP: github.com/goaop


                        1. VolCh
                          20.02.2018 12:26

                          Вот прямо сейчас сижу и отлавливаю баг, который скорее всего с этим связан. :(


                    1. Fesor
                      19.02.2018 17:11

                      ты про прокси классы?


                      1. SerafimArts
                        20.02.2018 00:56

                        Ага, они самые =(


  1. Fesor
    18.02.2018 17:21
    +2

    Налицо явное непонимание идеи ООП.


    но это тот самый оверхэд от которого можно отказаться.

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


  1. Fesor
    18.02.2018 17:41
    +1

    не обязательно создавать объекты в ООП

    вот только это не ООП а структурное программирование в лучшем случае. Идея ООП в том, что у вас не просто есть объекты, а в том что они взаимодействуют между собой, что есть late binding. Что до оверхэда — если данные не будут покидать приделов объектов, и если вы выкидываете из уравнения модель выполнения "обработай и умри" — то оверхэд будет как раз таки не в вашей реализации.


    то есть банальный пример. Продемонстрируйте мне как будет выглядеть late biding в контексте статических методов? Скажем, такой простой шаблон как декоратор реализуйте.


  1. Lure_of_Chaos
    19.02.2018 08:40

    Негодую и соболезную.
    Мне, как и многим моим коллегам, приходилось сталкиваться с ситуацией, когда расширение и поддержка существующего кода — чистый ад, а возможности все это отрефакторить к такой-то матери — нет, гонят новыми фичами. И что обидно — джуники и матерые рукожопы здесь на коне, потому что за 5 минут могут навалять косо-криво работающее решение, а шишки будешь получать ты, опытный разработчик, потому что уже полдня возишься и не можешь исправить эту «маленькую» но неприятную ошибку, исправление которой поднимает на поверхность все новые и новые косяки давно отошедших от проекта коллег…

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


    1. 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. Это, конечно, не без создания объектов, но проблемы там примерно те же.


  1. index0h
    20.02.2018 02:11

    создавался объект mysqli и присваивался свойству sql каждого объекта. Можно только представить сколько лишних действий в пустую и какой перерасход оперативной памяти происходит.

    Да, ссылки нынче дорогие(((


    Наверно сравнение на «сверхмалых» числах не дает полной картины

    И смысла не имеет, если на то пошло


    но это тот самый оверхэд от которого можно отказаться.

    Это экономия на спичках


    Причина не любви к работе с объектом (при передаче другому методу), в том что у меня есть фобия что он не тот за кого себя выдает

    Type hinting появился еще в 5.1


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

    Как же инкапсуляция?


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


    Но, в большинстве случаев статика приводит к:


    1. Бесконтрольной связности и хрупкости проекта

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


    1. Экспоненциальному росту сложности тестирования

    Ваши тесты будут на прямую зависеть от реализации статических методов и вы их не сможете подменить.


    1. Статика с состоянием — это пичалька

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


  1. ElectroGuard
    20.02.2018 11:47

    Стоит всё таки разобраться в ООП прежде чем его хаять. Касается, впрочем, не только ООП.


  1. Standfest
    20.02.2018 15:13

    Статья написана RedComrad'om, а стыдно мне.