ООП без «О» -28



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


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

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

    • AlexLeonov
      /#10676050 / +1

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

      • kruslan
        /#10676268

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

        • AlexLeonov
          /#10676496

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

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

          • Fesor
            /#10676874

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

            • AlexLeonov
              /#10677748

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

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

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

              • kruslan
                /#10677796

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

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

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

                • VolCh
                  /#10677978

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

                • AlexLeonov
                  /#10678262

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

                  Неважно.

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

                  • kruslan
                    /#10678348

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

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

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

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

                  • VolCh
                    /#10678928

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

              • Fesor
                /#10678254

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

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

  2. alex6636
    /#10675972 / +2

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

  3. garex
    /#10675996 / +4

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


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

  4. vitaliy2
    /#10676042 / +1

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

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

    • VolCh
      /#10676086 / +1

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

  5. AlexLeonov
    /#10676048 / +6

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

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

    echo A::someStatic()->prop;

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


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

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

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

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

  6. VolCh
    /#10676082 / +1

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

    • AlexLeonov
      /#10676142

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

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

      • Fesor
        /#10676148

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

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


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

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

        • AlexLeonov
          /#10676162

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

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

          • Fesor
            /#10676170

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

      • VolCh
        /#10676320

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

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

        • AlexLeonov
          /#10676402

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


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


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

          • Fesor
            /#10676406

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

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

            • AlexLeonov
              /#10676490

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

              • Fesor
                /#10676878

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

                • AlexLeonov
                  /#10677742

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

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

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

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

                  • Free_ze
                    /#10677856

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

                    • AlexLeonov
                      /#10678266

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

                      • Fesor
                        /#10678724

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

                        • AlexLeonov
                          /#10678842

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

                          • Fesor
                            /#10679164

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


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


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


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

                            • AlexLeonov
                              /#10680274

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

                        • VolCh
                          /#10678934

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


                          :)

                      • VolCh
                        /#10679270 / +1

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

                        • AlexLeonov
                          /#10680272

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

                  • Fesor
                    /#10678252

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

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


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


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

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

              • VolCh
                /#10676936 / +1

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

                • AlexLeonov
                  /#10677740

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

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

                  Спасибо

                  • VolCh
                    /#10677956

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


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

          • VolCh
            /#10676468

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

            • AlexLeonov
              /#10676492

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

              • SerafimArts
                /#10676504

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

              • Fesor
                /#10676892

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

              • VolCh
                /#10676932

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


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

                • Fesor
                  /#10677026

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


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

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

                  • VolCh
                    /#10677146

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

                  • SerafimArts
                    /#10677322

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

                    • VolCh
                      /#10677564

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

                      • SerafimArts
                        /#10678322

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

                        • VolCh
                          /#10678952

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

  7. Fesor
    /#10676152 / +2

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


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

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

  8. Fesor
    /#10676174 / +1

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

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


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

  9. Lure_of_Chaos
    /#10676604

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

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

    • ToshiruWang
      /#10676634

      Согласен, за рефакторинг такого лучше не приниматься — захочется убивать людей.
      Код друзей таких авторов
      BaseClass, ClassA : BaseClass, ClassB : BaseClass, ...
      ClassA a;
      ClassB b;
      ClassC c;
      if(a != null)
      {
      a->someMethod();
      } // Даже без else, хотя инициализируется только одна переменная и группы
      if(b != null)
      {
      b-> someMethod();
      }

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

      PS. Это, конечно, не без создания объектов, но проблемы там примерно те же.

  10. index0h
    /#10678380

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

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


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

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


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

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


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

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


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

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


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


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


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

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


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

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


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

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

  11. ElectroGuard
    /#10678772

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

  12. Standfest
    /#10679462

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