Добрый день уважаемые форумчане. Сейчас активно читаю книгу Мэтта Зандстры, параллельно пытаюсь определиться с архитектурой своего приложения. Задача пока простая: хочу написать чат-бота для сообщества ВК, у ВК для разработчиков есть 2 API, это Callback API (происходит событие в сообществе - отправляется запрос с информацией о событии на наш сервер) и соответственно публичное API, его дёргаем уже мы (отправляем сообщения от имени сообщества и т.д.). У ВК на самом деле есть огромный перечень событий на которые можно реагировать, но меня интересует исключительно событие входящего сообщения (кто-то написал в личку сообществу). В итоге кейс выглядит как: спроектировать приложение, которое бы выполняло некоторую клиентскую логику в ответ на те или иные параметры входящего сообщения. Я определился так, раз это чат-бот, то мне нужно лишь одно событие - message_new, то пусть это будет лишь один конкретный класс - IncomingMessage (это будет вроде аналогии Request во многих крупных фреймворках). Так-же мне нужен класс приложения, который будет включать в себя конфиг, как-то обрабатывать событие и вызывать соответствующий параметру класс (в событии есть такая вещь как payload, так вот один из параметров будет говорит о том, какой класс должен быть вызван, что-то типа контроллера в MVC, имя которого указанно в URL). Вроде-бы не много всего, верно? Но у меня уже на этом этапе ПОЛНЕЙШИЙ ступор, т.к. я не имею вменяемого опыта проектирования, а подсказать сейчас некому. IncomingMessage будет иметь минимальное количество полей (поля, кстати, будут наполняться из php://input, там лежит raw json события). Пусть это будет поле $peerID и $text. Если подумать, то экземпляр данного класса хорошо бы сделать одиночкой, ведь на всё приложение у нас лишь 1 общий экземпляр входящего сообщения, к которому скорее всего понадобиться иметь доступ из различных мест в коде. Сразу оговорюсь, приложение в итоге получится небольшое, и это не фреймворк, так-что не вижу смысл городить свой DiC или использовать готовый, на фоне задуманного это оверхед. Вернёмся к IncomingMessage PHP: <?php namespace Components\Callback; class IncomingMessage { private static $instance; private $peerID; private $text; private function __construct(int $peerID, string $text) { $this->peerID = $peerID; $this->text = $text; } public static function getInstance() { if (empty(self::$instance)) { $data = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR); self::$instance = new IncomingMessage($data['object']['peer_id'], $data['object']['text']); } return self::$instance; } } У нас что-то вроде этого. Окей, определились. Но вот в чём штука, у нас есть класс приложения, пусть он будет называться просто Bot. Бот при создании экземпляра будет требовать массив параметров конфигурации, а также метод для получения необходимого значения (геттер). PHP: <?php namespace Components\Bot; class Bot { private $config; public function __construct(array $config) { $this->config = $config; } public function getParam(string $path) { return array_reduce(explode('.', $path), function ($c, $i) { return $c[$i] ?? null; }, $this->config); } } Клиентский код всего этого дела пока выглядит так: PHP: <?php declare(strict_types=1); require __DIR__.'/../vendor/autoload.php'; $config = require __DIR__.'/../config.php'; $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? Короче, я не знаю что делать, когда говорит автор статьи или книги на своих примерах, выглядит вроде убедительно, как пытаешься что-то из этого применить в своих кейсах, возникает множество вопросов и противоречий. Как бы вы реализовали подобную задачу и почему? Мне кажется меня вообще заруливает не в ту сторону, и я задаюсь не правельными вопросами.
Ну смотри, сначала же происходит POST запрос, который принимает это сообщение. Т.е. прежде всего нужен класс обработки пост-запроса. Твой бот пока непонятная штука, что-то типа объекта конфига, а это что, его основная задача? Класс обработки пост-запроса должен уже всё это раскрутить. Если бы я писал, то написал бы что-то типа роутера для сообщений от коллбекков, который бы создавал объекты-обработчики для каждого сообщения. И соответственно, класс типа NewMessageHandler. Нужен ли объект IncomingMessage - не уверен, может и нужен. В любом случае, разобрать php://input - это задача того, кто дёргает handler. А что, контакт тебе только это событие будет слать? И каждый класс надо стремиться по-возможности сделать универсальной штукой. Сегодня ты только одно событие обрабатываешь, завтра понадобится ещё 10. И все забывают одну вещь. ООП - это всего лишь способ декомпозиции алгоритма. Пиши сначала алгоритм. Хочешь - просто по-русски, хочешь через UML, только не обязательно сразу диаграмму объектов. http://it-gost.ru/articles/view_articles/94 --- Добавлено --- Вот я на работе недавно с мобильщиком час обсуждал решение задачи. Он не знает php, я без понятия, как программировать на Android, но итогом часового мозгового штурма стало описание по шагам на русском языке, что должен сделать сервер, и что должно сделать приложение. Там не было агрегаций, ассоциаций и прочего. Там был на высоком уровне разработан алгоритм. Потом уже каждый его шаг был детализирован, появились объекты и прочая мишура. --- Добавлено --- Это что, у Зандстры такое определение агрегации? Когда передаёшь в конструктор? Мне что-то немного по-другому помнится
Да, исключительно его, это настраивается в настройках сообщества. И мне необходимо только это событие. У Зандстры написано: Отсюда делается вывод что в контексте PHP что-бы реализовать агрегацию, нам нужно создать объект во вне, и передать его в конструктор другого класса. Если контейнер будет удалён, то объект который мы передавали останется, т.к. на него есть ещё одна ссылка, в отличии от случая композиции, где мы создали бы инстанс прямо внутри конструктора, и на этот объект ссылалось лишь поле класса-конейнера.
@equentor, короче, начинай с алгоритма. А потом уже всякую композицию/агрегацию будешь делать. А может и вовсе придёшь к выводу, что тебе для этой задачи ООП не нужно --- Добавлено --- А собственно, почему тупо?
Господа, вы меня ради Бога извините. Но мне кажется, что за сими высокопарными словами теряется суть задачи. Три раза перечитал - понял, что мой опыт программирования (и не только на пхп) предельно слаб))) Неужели нельзя более ясно выразить суть проблемы? --- Добавлено --- Я обычно делаю так: есть разрозненные классы, есть родительский класс, который их дергает по необходимости. ВСЁ!
Ну вот чел инстанс синглтона считает жутко неправильным передать в конструктор, переживает --- Добавлено --- Ты докуда, кстати, вообще дочитал? Одиночка - это один из самых простых паттернов, уже практически вышел из употребления. Фабрики читал?