За последние 24 часа нас посетили 22598 программистов и 1006 роботов. Сейчас ищут 750 программистов ...

Как определиться с архитектурой и отношениями?

Тема в разделе "PHP для новичков", создана пользователем equentor, 9 июл 2019.

Метки:
  1. equentor

    equentor Новичок

    С нами с:
    20 апр 2017
    Сообщения:
    4
    Симпатии:
    0
    Добрый день уважаемые форумчане.
    Сейчас активно читаю книгу Мэтта Зандстры, параллельно пытаюсь определиться с архитектурой своего приложения. Задача пока простая: хочу написать чат-бота для сообщества ВК, у ВК для разработчиков есть 2 API, это Callback API (происходит событие в сообществе - отправляется запрос с информацией о событии на наш сервер) и соответственно публичное API, его дёргаем уже мы (отправляем сообщения от имени сообщества и т.д.). У ВК на самом деле есть огромный перечень событий на которые можно реагировать, но меня интересует исключительно событие входящего сообщения (кто-то написал в личку сообществу).

    В итоге кейс выглядит как: спроектировать приложение, которое бы выполняло некоторую клиентскую логику в ответ на те или иные параметры входящего сообщения.

    Я определился так, раз это чат-бот, то мне нужно лишь одно событие - message_new, то пусть это будет лишь один конкретный класс - IncomingMessage (это будет вроде аналогии Request во многих крупных фреймворках).

    Так-же мне нужен класс приложения, который будет включать в себя конфиг, как-то обрабатывать событие и вызывать соответствующий параметру класс (в событии есть такая вещь как payload, так вот один из параметров будет говорит о том, какой класс должен быть вызван, что-то типа контроллера в MVC, имя которого указанно в URL).

    Вроде-бы не много всего, верно? Но у меня уже на этом этапе ПОЛНЕЙШИЙ ступор, т.к. я не имею вменяемого опыта проектирования, а подсказать сейчас некому.

    IncomingMessage будет иметь минимальное количество полей (поля, кстати, будут наполняться из php://input, там лежит raw json события). Пусть это будет поле $peerID и $text. Если подумать, то экземпляр данного класса хорошо бы сделать одиночкой, ведь на всё приложение у нас лишь 1 общий экземпляр входящего сообщения, к которому скорее всего понадобиться иметь доступ из различных мест в коде.

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

    Вернёмся к IncomingMessage
    PHP:
    1. <?php
    2.  
    3. namespace Components\Callback;
    4.  
    5. class IncomingMessage
    6. {
    7.     private static $instance;
    8.     private $peerID;
    9.     private $text;
    10.  
    11.     private function __construct(int $peerID, string $text)
    12.     {
    13.         $this->peerID = $peerID;
    14.         $this->text = $text;
    15.     }
    16.  
    17.     public static function getInstance()
    18.     {
    19.         if (empty(self::$instance)) {
    20.             $data = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
    21.             self::$instance = new IncomingMessage($data['object']['peer_id'], $data['object']['text']);
    22.         }
    23.  
    24.         return self::$instance;
    25.     }
    26. }
    У нас что-то вроде этого. Окей, определились. Но вот в чём штука, у нас есть класс приложения, пусть он будет называться просто Bot. Бот при создании экземпляра будет требовать массив параметров конфигурации, а также метод для получения необходимого значения (геттер).

    PHP:
    1. <?php
    2.  
    3. namespace Components\Bot;
    4.  
    5. class Bot
    6. {
    7.     private $config;
    8.  
    9.     public function __construct(array $config)
    10.     {
    11.         $this->config = $config;
    12.     }
    13.  
    14.     public function getParam(string $path)
    15.     {
    16.         return array_reduce(explode('.', $path), function ($c, $i) {
    17.             return $c[$i] ?? null;
    18.         }, $this->config);
    19.     }
    20. }
    Клиентский код всего этого дела пока выглядит так:
    PHP:
    1. <?php declare(strict_types=1);
    2.  
    3. require __DIR__.'/../vendor/autoload.php';
    4. $config = require __DIR__.'/../config.php';
    5. $bot = new \Components\Bot\Bot($config);
    далее курсивом выделен примерный поток моего сознания, который постоянно заводит в различные тупики

    И встаёт вопрос, а как вообще обеспечить взаимодействие этих компонентов? Агрегация? Композиция? Отношение использования (метод в классе Bot, который принимает IncomingMessage в виде аргумента)? Нужно ли мне при конструировании Bot добавить в конструктор вторым аргументом параметр типа IncomingMessage, или раз это синглтон, то не нужно? Но тогда (тёмная сторона одиночки) у меня получается не явная зависимость. Но постойте, может быть стоит зайти с другой стороны и подумать категориями реальных объектов? Что без чего не имеет смысла? В принципе Bot без IncomingMessage даже не запустится, соответственно это, наверное, композиция, и может быть мне лучше инстанцировать через new экземпляр IncomingMessage, чёрт, да там и обратное справедливо, IncomingMessage не имеет смысла вне контекста приложения? Постойте, но ведь он же одиночка, его конструктор закрыт и его статический метод InvoiceMessage::getInstance() доступен везде. Это ведь тупо иметь и глобальный доступ к объекта, и при этом передавать его в конструктор (агрегация), а уж темболее присваивать свойству внешнего объекта через $this->incomingMessage = IncomingMessage::getInstance(); по причине того же глобального доступа.

    Но НЕ одиночкой он ведь быть не может, у него должен быть закрыт конструктор, что-бы точно обезопаситься от new IncomingMessage. Или такая защита от дурака это вообще перебор? Да и от кого защищать, это же не фреймворк, и пишу это только я.

    А может всё дело в том что я не правильно понял что есть ассоциация, композиция и агрегация? Да вроде-бы понял, прочитал не одну статью. Может быть это вообще больше про UML чем про реализацию в конеретном языке? А может быть это всё нужно больше для долгоживущей модели приложения, а не для умирающей модели как в PHP?


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

    Мне кажется меня вообще заруливает не в ту сторону, и я задаюсь не правельными вопросами.
     
    #1 equentor, 9 июл 2019
    Последнее редактирование: 9 июл 2019
  2. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.553
    Симпатии:
    1.754
    Ну смотри, сначала же происходит POST запрос, который принимает это сообщение. Т.е. прежде всего нужен класс обработки пост-запроса. Твой бот пока непонятная штука, что-то типа объекта конфига, а это что, его основная задача?

    Класс обработки пост-запроса должен уже всё это раскрутить. Если бы я писал, то написал бы что-то типа роутера для сообщений от коллбекков, который бы создавал объекты-обработчики для каждого сообщения. И соответственно, класс типа NewMessageHandler. Нужен ли объект IncomingMessage - не уверен, может и нужен. В любом случае, разобрать php://input - это задача того, кто дёргает handler.

    А что, контакт тебе только это событие будет слать? И каждый класс надо стремиться по-возможности сделать универсальной штукой. Сегодня ты только одно событие обрабатываешь, завтра понадобится ещё 10.

    И все забывают одну вещь. ООП - это всего лишь способ декомпозиции алгоритма. Пиши сначала алгоритм. Хочешь - просто по-русски, хочешь через UML, только не обязательно сразу диаграмму объектов. http://it-gost.ru/articles/view_articles/94
    --- Добавлено ---
    Вот я на работе недавно с мобильщиком час обсуждал решение задачи. Он не знает php, я без понятия, как программировать на Android, но итогом часового мозгового штурма стало описание по шагам на русском языке, что должен сделать сервер, и что должно сделать приложение. Там не было агрегаций, ассоциаций и прочего. Там был на высоком уровне разработан алгоритм. Потом уже каждый его шаг был детализирован, появились объекты и прочая мишура.
    --- Добавлено ---
    Это что, у Зандстры такое определение агрегации? Когда передаёшь в конструктор? Мне что-то немного по-другому помнится
     
    #2 mkramer, 9 июл 2019
    Последнее редактирование: 9 июл 2019
    acso нравится это.
  3. equentor

    equentor Новичок

    С нами с:
    20 апр 2017
    Сообщения:
    4
    Симпатии:
    0
    Да, исключительно его, это настраивается в настройках сообщества. И мне необходимо только это событие.
    У Зандстры написано:

    Отсюда делается вывод что в контексте PHP что-бы реализовать агрегацию, нам нужно создать объект во вне, и передать его в конструктор другого класса. Если контейнер будет удалён, то объект который мы передавали останется, т.к. на него есть ещё одна ссылка, в отличии от случая композиции, где мы создали бы инстанс прямо внутри конструктора, и на этот объект ссылалось лишь поле класса-конейнера.
     
  4. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.553
    Симпатии:
    1.754
    @equentor, короче, начинай с алгоритма. А потом уже всякую композицию/агрегацию будешь делать. А может и вовсе придёшь к выводу, что тебе для этой задачи ООП не нужно
    --- Добавлено ---
    А собственно, почему тупо?
     
  5. acso

    acso Активный пользователь

    С нами с:
    15 апр 2010
    Сообщения:
    150
    Симпатии:
    25
    Адрес:
    Одесса
    Господа, вы меня ради Бога извините. Но мне кажется, что за сими высокопарными словами теряется суть задачи. Три раза перечитал - понял, что мой опыт программирования (и не только на пхп) предельно слаб))) Неужели нельзя более ясно выразить суть проблемы?
    --- Добавлено ---
    Я обычно делаю так: есть разрозненные классы, есть родительский класс, который их дергает по необходимости. ВСЁ!
     
  6. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.553
    Симпатии:
    1.754
    Ну вот чел инстанс синглтона считает жутко неправильным передать в конструктор, переживает :)
    --- Добавлено ---
    Ты докуда, кстати, вообще дочитал? Одиночка - это один из самых простых паттернов, уже практически вышел из употребления. Фабрики читал?