PHP и ООП. Совмещаем \«несовмещаемое\»…

    Объектно-ориентированное программирование — как стиль жизни. Это не просто применение конструкций типа class или interface — это способ мышления, когда любая сущность программы является не просто набором инструкций, а представляет из себя \«живое\» существо…

    Думаю напоминать что такое ООП, в рамках данной статьи, будет лишним, посему перейду сразу к проблематике вопроса.

    Разработчики, пришедшие в PHP из других языков программирования, и столкнувшиеся в нем (в РНР. тут и далее буду подразумевать РНР5 — прим.) с классами, недоумевают как их вообще возможно использовать. А все из-за того, что время жизни скрипта на РНР гораздо меньше, чем у прикладного ПО и составляет всего один цикл работы, в то время как прикладное ПО может жить и взаимодействовать со своими компонентами много дольше. В итоге миру является код, в котором классы реализуют всего-навсего отсутствующие в РНР namespaces.

    class A {
        public static function b() {}

        public static function c() {}

        public static function d() {}
    }

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

    User::register($name, $pwd);
    //... a lot of code ...
    System::Log($message, $code);

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

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

    Хоть цикл жизни скрипта всего один, но его можно прожить красиво! Этим и попытаемся заняться…
    Представим что у нас есть социальная сеть (ну а как же нынче без них? :)); в этой соц.сети есть пользователи, а каждый пользователь имеет свой блог. А теперь попытаемся лаконично и элегантно отобразить сферы действия каждой из представленных сущностей.
    Пользователь имеет набор свойств. Пусть это будут: логин, пароль и email. Пользователь может совершать некоторые действия: логиниться, выходить, создавать блог и писать в него.
    Блог в свою очередь имеет свойствами: автора, коллекцию топиков и дату создания. Блог может добавлять в себя запись.
    Ограничимся этим набором методов и свойств сущностей. Как элегантнее всего представить подобную структуру в коде? Предлагаю следующий вариант:
    class Blog {
        public $topics_list;
        public $creation_date;
        private $data; //свойства объекта, взятые из БД

        public function __construct($id) { //id блога
            //где-то тут заполняем $this->data, к примеру из БД
        }

        public function getAuthor() {
            static $author; //объявим пользователя статическим, что бы не расходовать ресурсы в случае, если получать пользователя нам не прийдется, а так же оградимся от неправильного доступа к этому псевдо-свойству класса

            if (empty($author)) {
                $author = new User($this->data[\'author_id\']);
        }

            return $author;
        }

        public function addTopic(Topic $topic) { ... } //метод описывать не буду. Главное понимать что он делает
    }

    class User { ... } //описывать пользователя не буду, что бы не нагромождать код, но смысл его методов будет подобный


    Итак — чего мы добились? Создав оба класса подобным образом в итоге мы имеем четкие области ответственности каждого объекта, к тому же мы не будем заворачиваться с передачей ненужных параметров. К примеру создадим запись в блоге \«по-старинке\» и исходя из описанных макетов.
    По-старинке:
    $user_id = $_SESSION[\'user_id\']; //предполагаем идентификацию пользователя по его id из сессии

    $topic_message = \"Новый топик\";
    Blog::addTopic($user_id, $topic_message); //укажем для какого пользователя добавляется запись

    Все вроде бы понятно и естественно, кроме одного — при передачи аргументов для создания топика, становится не очевидным роль пользователя. Посмотрим как будет выглядеть этот же код с новым подходом:
    $user = new User($_SESSION[\'user_id\']);
    $topic_message = \"Новый топик\";

    $user->getBlog()->addTopic($topic_message);

    Тут гараздо очевиднее взаимоотношения между блогом и пользователем… А если подумать что топик — тоже объект и у него есть свойство \«сообщение\», то получить его мы моглибы так:
    $user = new User($_SESSION[\'user_id\']);
    echo $user->getBlog()->getTopics()->topic[$topic_id]->message;


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

    Если после прочтения появились вопросы, буду рад на них ответить.

    P.S. Подскажите как оформлять участки кода в топиках на Хабре, а то как-то не читабельно у меня получилось :(
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

    Комментарии 62

      +1
      все замечательно, единственное я бы заменил
      echo $user->getBlog()->getTopics()->topic[$topic_id]->message;
      на
      echo $user->getBlog()->getTopic($topic_id)->message;
      дабы не создавать класс, представляющий из себя массив топиков.
      да и разруливать ситуацию, когда запрашивается отсутствующий топик так гораздо проще.
      а в целом объекты — наше все.
        0
        когда-то хотел было сделать именно так, но сама коллекция топиков может иметь методы. например ->clearAll()
          0
          коллекция топиков чересчур абстрактная сущность. $blog->clearAllTopics() — значительно более прозрачное действие удаления всех топиков блога (хотябы явно видно что удаляються топики конкретного блога).
          ну и соответственно $user->clearAllTopics();
          по крайней мере я всегда делаю так, но было бы интересно подискутировать на эту тему).
            0
            Я думаю автор хотел донести общую идею, а не предложить реализацию какой-то конкретной задачи.
              0
              да, вы правы. просто для меня эта тема актуальна и интересна, вот я и поинтересовался его мнением по этому конкретному случаю.
              0
              ммммм.... $blog->clearAllTopics() - вкусный пример :), но вот с $user->clearAllTopics() поспешу не согласиться. Не соглашусь по причине излишней связанности классов. Как итог будем иметь проблемы расширяемости приложения, а так же не совсем четкую логику - снова возвращемся к неймспейсам :)
              Сохраняя иерархию объектов, получаем более логичную структуру
                +1
                в чем то вы правы.
                Однако при увеличении количества блогов и топиков операция удаления всех топиков пользователя происходила бы путем перебора всех блогов юзера и последовательным вызовом $blog->clearAllTopics();
                Притом возможность работы напрямую со всеми топиками юзера непременно может понадобиться (например для создания rss ленты юзера), и тогда удобно будет использовать $user->getAllTopics() ну или в дальнейшем $user->getAllComments();

                В любом случае связь между объектами Blog, User и Topics очень тесная и совсем отделить их друг от друга не получиться (да и нужно ли?).
                  +2
                  не получиться только контексте приложения, которое я описал, а если наша система завязана не только на блоги, но и на многие другие аспекты жизнедеятельности пользователя на портале? Ссылаться на специфические функции отдельных элементов системы из объекта пользователя - это уже некоторый перебор по-моему
                    0
                    да-да-да
                    0
                    За создание rss ленты пользователя должен отвечать метод пользователя: $user->getBlogsAllRSS() & $user->getBlogRSS(), а за список топиков блога — метод блога.
                    Увеличение изоляции избавляет от головных болей.
                      0
                      извините, поторопился :)
                      $user->getRSSBlogsAll() & $user->getRSSBlog()
                        0
                        А мне мерещится что-то типа
                        $rss->findByUser($user->getId());
                        $rss->findByBlog($blog->getId());
                        и
                        $posts->findByBlog($blog->getId());
                          0
                          коль уж сущности очевидны, то и быть им реализованными в отдельных классах
                            0
                            С первыми согласен, сущность отдельная, а вот посты сущность внутренняя для блога, не стал бы разделять.
                              0
                              А кто говорит, что они только к блогам могут относиться? ;)
                                0
                                дык конкретный пример рассматриваем, так ведь можно скатиться к общим интерфейсам списков и слова Blog и Comments потеряют здешний смысл :)

                                $rss->get( SomeArrayList list, Hashtable fields );
                                +1
                                "Внутренняя" — это инкапсулированная коллекция постов внутри блога? Или всё же связанная?
                                  0
                                  Я имел в виду инкапсулированную коллекцию объктов.
                              –1
                              Думаю сущность rss не должна включать сущности других объектов, дабы мы никогда не знаем сколько етих сущностей будет в будущем.
                              Скорее сущность rss должна описывать какие-то базовые действия для генерации rss и инкапсулироваться :)

                              $user->getBlogs()->getRss();

                              :)
                              +2
                              Чем rss отличается от обычной html ленты постов? По-моему, только способом представления данных, т.е. шаблоном. Получается что для отдельного вида шаблонов (RSS) мы создаем новую сущность (объект, класс) и тем самым плодим их (сущности).
                              На мой взлад для создания rss ленты нам нужно две вещи: массив данных и xml-шаблон для их вывода. Зачем объекту юзер думать о том, в каком виде (шаблоне) его данные будут выводиться, зачем ему вообще о шаблонах знать что-то?

                              Другое дело что подготовка массива данных для вывода в xml-шаблоне довольно рутинная операция: нужно привести массив к определенному стандартному виду (например к полям типа title, description, date, url и т.д.). И для этого возможно понадобиться класс, который эту рутину будет максимально просто выполнять. Для примера можно посмотреть Zend_Feed, который именно этим и занимается.

                              А добавлять логику работы с rss в $user или $blog и прочие объекты, на мой взгляд, не стоит.
                              имхо разумеется)
                                +1
                                а может описать это паттерном Стратегия? :)
                                  +1
                                  да. это уже способ реализации. я думаю что в данном конкретном случае Strategy, а значит и классы-реализации стратегии излишни. хотя все зависит от обстоятельств конечно. кстати, раз речь о патернах: как можно назвать патерн, который реализует Zend_Feed? Декоратор, агрегатор... не силен в теории.
                                    0
                                    Да как угодно описать можно, но сути это не меняет: юзеры — это юзеры, а посты — это посты. И последние можно (да и нужно будет) не только в контексте пользователя выбирать. Лучше уж отдельно их.
                                      0
                                      А низ-зя, так как нам ясно сказали, что это коллекция, инкапсулированная в блог. Сослаться на все комменты юзера к постам во всех блогах — это непонятно решаемая задача в этой архитектуре.
                                    0
                                    и вам тоже "да-да-да" )
                        +1
                        Где тут табличка "Аплодисменты" :)
                        Пример хорошо показывает, что при программировании надо пользоваться естественным языком.

                        Многие сразу садятся на за написание кода, а для начала следовало бы обдумать все сущности и действия над ними, а это нормальный человек делает на своём родном языке. Потому и код потом читается по-людски, а не по-писишному :)
                          0
                          Это всё конечно прекрасно. До того момента когда во время проекта приходится пересматривать сущности.
                          Я думаю, что "пересмотром" кода надо занимать в конце проекта. Т.е. рефакторингом кода.
                          Да, согласен, основную архитектуру надо продумать сразу и описать сразу, но сущности... могу поспорить в большом проекте они будут пересматриваться не один раз и не два. Вот тогда можно и запутаться в сущностях (что откуда и куда).
                          Вот когда проект подошел к beta тогда и можно рефакторить и переводить в сущности и т.п. чтобы в будущем было уже легко денормализировать и масштабировать проект.
                          0
                          А зачем сделаны открытыми атрибуты блога? Вы собираетесь их менять извне?
                            0
                            собственно пример :) насчет методов доступа могу закросспостить еще статью из своего блога, если интересно...
                          +3
                          1. Никогда не понимал чепухи про то, что "время жизни скрипта PHP намного меньше". Меньше чего? Меньше для кого? С точки зрения человека? Причем тут человек? Для процессора время жизни скрипта - миллиарды тактов, за это время можно много чего успеть.

                          Видимо, этими словами пытаются выразить другой аспект - PHP-сценарий не интерактивен. В процессе выполнения, ему не нужно реагировать на действия пользователя. Но тут он ничем не отличается от других неинтерактивных процессов, пусть они хоть неделями крутятся.

                          Если понять основную задачу PHP - формирование ответа на основании запроса - и не пытаться эмулировать интерактивность, то оказывается, что ООП в PHP ничем в принципе не отличается от ООП в других областях.

                          2. По моему скромному ИМХУ, сравнение программных объектов и объектов окружающего мира, одна из самых вредных привычек программистской литературы. Сколько неподготовленных мозгов забито этой догмой, не пересчитать.
                            +1
                            касательно первого - да, именно это и подразумевалось, но выразил суть таким образом, чтобы было понятно всем, а не только 10% читателей
                            касательно второго... нууу тут я позволю себе не согласиться. Тождестевнность подобного рода позволяет развить понимание ООП, как явления, а не просто пользоваться его техническими преимуществами на уровне языка программирования.

                            в дополнение. В данном топике я старался раскрыть лишь подход к реализации взаимодействий объектов, а не подменять понятия
                              +1
                              IMHO тут имеется в виду сессионный характер работы скрипта, т.е. мы поднимаем мегабайт объектного кода для выдачи одной странички. Вот и возникают вопросы целесообразности.

                              Примеры реальных объектов приводятся для начинающих, которым проще оперировать, тем, что можно пощупать. Но одно из главных преимуществ ООП в возможности моделирования абстрактных объектов, а к ним надо переходить после практики с реальными, так просто проще большинству.
                                –1
                                Так не надо мегабайты. На ООП можно и компактно писать. Опять таки здесь весьма субъективная мера. "Для выдачи одной странички" - почему к страничке нужно относиться пренебрежительно?


                                Примеры реальных объектов приводятся для начинающих, которым проще оперировать, тем, что можно пощупать

                                Да, и эти примеры хороши на первых двух страницах объяснения. А дальше это уже больше похоже на беспомощность автора в объяснении сути ООП.
                                  0
                                  Есть ведь еще и сторонние библиотеки и комментарии нельзя хранить отдельно. И 840к кода текущего проекта мной написано только 200 (шаблоны, конечно, не в счет).
                                  IMHO компактный код — признак недостаточного абстрагирования, на других проектах придется перетачивать.

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

                                  От автора зависит :) Бадд в "ООП" тоже начинает с объктов реального мира (цветочница, покупатели, товар и прочее), потом ничего так :) для введения.
                                    0
                                    Абстракция не эквивалент ООП. Можно и без ООП абстрактно писать, можно и с ООП не впадать в излишние крайности.
                                      0
                                      Да, конечно, но с ООП это делать значительно проще. Зачем прикладнику себя мучать?
                                      0
                                      > IMHO компактный код — признак недостаточного абстрагирования, на других проектах придется перетачивать.

                                      Смысл абстрагирования — в написании мегабайтов кода?
                                        0
                                        Смысл в наименьших усилиях, прилагаемых программистом, при изменении начальных условий или при старте нового проекта.
                                        Надо каждый проект начинать с полного конструирования?
                                        Но это все надо понимать без фанатизма, сам не люблю мегамонстров.
                                          0
                                          У меня есть своя абстрактная библиотека и куча реюзабельных классов под неё. В частности, сейчас на http://nav.academ.org/freedom/ кода под, собственно, проект — 22 килобайта. Скорость генерации страниц — от двух до пяти миллисекунд.

                                          И код при этом достаточно компактен.
                                            0
                                            эти 22 кб делают всё всё?
                                              0
                                              В рамках данного проекта — да. Ещё раз повторю, что ни библиотеку, ни классы, которые реюзаются, я не считаю.
                                                0
                                                Т.е. ты так и сделал: написал мегабайты абстракций и уложил всю работу с ними в 22кб, почему ты тогда на меня нападаешь :)
                                                  0
                                                  Почему мегабайты?

                                                  http://alexeytokar.habrahabr.ru/blog/372…
                                                    0
                                                    Это спор ни о чем, сам перечитай:

                                                    >кода под, собственно, проект — 22 килобайта
                                                    и
                                                    >что ни библиотеку, ни классы, которые реюзаются, я не считаю

                                                    ну а для одного списка и поиска по нему, наверное даже много, админка наверное съела.
                                                      0
                                                      Спора нет. Разве что с моей стороны. Есть дискуссия.

                                                      Код вполне абстрактный, но компактный.
                                                      Что касается объёма — не много. Четыре вьюшки, контроллер, три модели. Если на 22 разделить на 8, получается меньше трёх килобайт на класс (включая комментарии).
                                              0
                                              жаль что не качается...
                                              али там только для своих?
                                                0
                                                Если про анимки — да, они доступны из новосибирских локальных сетей.
                                  0
                                  что есть несомненно плюсом - это Вы о чем? :-)
                                    –1
                                    о том что чем больше кода, не требующего комментариев, тем лучше :)
                                    +1
                                    С одной стороны про статический вариант Вы пишите "имеет массу плюсов", с другой стороны "давайте опишем очки". Где вывод?

                                    Почему роль пользователя неочевидна для этой строки кода

                                    Blog::addTopic($userId, $topic_message);


                                    и почему она проясняется в этой строке

                                    new User($user_id)->getBlog()->addTopic($topic_message);


                                    и почему

                                    Blog::getMessage($messageId);


                                    хуже

                                    new User($_SESSION['user_id'])->getBlog()->getTopics()->topic[$topic_id]->message;
                                      +1
                                      "масса плюсов" еще не означает "неоспоримое преимущество" - не надо в крайности впадать :)

                                      насчет недостатков и преимуществ:
                                      1) семантика
                                      2) логичность поведения участка кода (собственно то о чем вся статья)

                                      насчет последнего:
                                      Blog::getMessage($messageId); - вообще не ясно откуда взялся и что из себя представляет. Что за абстрактный блог? Просто запись в БД? Тогда еще прозрачнее написать:
                                      SELECT * FROM blog WHERE id = $messageId


                                      я не претендую на единственность решения и реализация. Я лишь хочу показать какой подход более логичен, а какой более непредсказуем
                                        0
                                        >> Blog::getMessage($messageId); - вообще не ясно откуда взялся и что из себя
                                        >> представляет. Что за абстрактный блог?

                                        Не стоит искать абстракцию там где ее нет. Строка кода, которая для Вас неясно откуда взялась (я ее сам придумал) означает на русском "получить комментарий блога по заданному идентификатору". Мне интересно услышать в чем нелогичность и непредсказуемость этой строки кода. Собственно я хочу понять суть неоспоримого преимущества, и сделать это можно развивая пример дальше, и показать, что один подход ведет в тупик, нелогичность и запутанность, а второй имеет красивое решение. Мне такого примера будет достаточно, я приму Вашу точку зрения и стану немного опытнее ;-)
                                      0
                                      Читаю комментарии и вижу проблему большинства программистов. Вы не можете абстрагироваться, отвлечься от конкретных вещей.

                                      Автор старался указать вам общую идею того, как правильно использовать ООП (конечно на полноту это не претендует), как надо начинать думать. А вы скатывайтесь до тупого обсуждения: чем это читабельнее того, обсуждаете примеры кода.

                                      Воспринимайте статью как рекомендацию. Считаете её правильной - возьмите на вооружение. Нет - так и скажите, аргументируя. Но зачем доказывать, что ямб лучше хорея :)
                                        0
                                        Автор как бы говорит нам — ООП принципиально хорошо? ) Дык тогда тут сейчас холивар вообще начнётся. Ау! Процедурно-функциональные адепты!?
                                        Лучше уж всё-таки так, как сейчас, пообщаться. Оно так полезнее и нагляднее. Да и отвлечённых споров не провоцирует. В общем — в рамках заданной темы лучше холиварить, чем вообще херить тему отвлечёнными постами =) Упс.
                                        Автору спасибо, что вот так вот запросто и просто поднял тему. Надеюсь, будет продолжать, усложнять, приводить примеры.
                                          0
                                          Усложнять не надо - пусть не отбивает желание читать посты :)
                                            0
                                            "Процедурно-функциональные адепты!?"

                                            Вот ить! Ни одного такого не видел.
                                              0
                                              пока меня медленно но уверенно занижают в карме, так что не обещаю, что к вечеру вообще окажусь "разговорчивым" :)))
                                                0
                                                Это процедурно-функциональные адепты :)
                                            +1
                                            Совмещаем "несовмещаемое"
                                            ооо!


                                            echo $user->getBlog()->getTopics()->topic[$topic_id]->message;

                                            ИМХО вместо message надо, getMessage()! Зачем давать доступ к свойствам напрямую????

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

                                          Самое читаемое