Ответы пользователя по тегу ООП
  • Связи в ООП, Агрегация в PHP как часто используется?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Основа всех современных фреймворков - это контейнер зависимостей. Который, создавая на лету экземпляр требующегося для обработки НТТР запроса класса, "внедряет" в него (через конструктор или через параметры методов) необходимые этому создаваемому объекту/вызываемому методу зависимости. Создавая их на лету или используя готовые.
    И, в свою очередь, создавая эти зависимости, также внедряет нужные уже им сервисы. И так по цепочке.

    Если вы посмотрите на пример реализации контроллера в Симфони например, то вы нигде не увидите что-то вроде
    public function process()
    {
        $request = new Request();
        $param = $request->get('name')
    }

    а только
    public function process(Request $request)
    {
        $param = $this->request->get('name')
    }


    Заметил, что в PHP если и используется связи между объектами(ассоциация), то только в виде композиции,

    А вот тут я реально удивился. Скорее это я бы у вас попросил "пример кода" в котором "вы заметили" композицию. Потому что в современном РНР коде её днем с огнём не найти - везде сплошная агрегация. Даже там, где по сути подошла бы и композиция. Давно ли вы видели в коде создание new Сlass внутри класса вместо использования $this->object? И если да - то мне страшно любопытно - где?

    Хотя нет, кажется я понял. Есть такие гении, которые действительно, вместо
    class User
    {
        public function __construct(Database $db)
        {
            $this->db = $db;
        }
    }

    пишут
    class User
    {
        protected $db;
        public function __construct()
        {
            $this->db = new Database();
        }
    }

    Но тут надо понимать, что это дети, которые вчера увидели редактор кода. Больше никто так не пишет.
    (Тут надо оговориться, что это не догма, и внедрение через параметры не обязательно. Вполне можно создать какой-то объект и на лету, но это случается гораздо реже, чем использование уже готового)

    редко бывает, когда создаётся самостоятельный класс, а потом он ещё и в другой класс попадает.

    А здесь вы вообще какую-то чушь написали. Если говорить про классы, то они вообще почти никогда не создаются кодом на лету. Классы пишет программист, и сохраняет в коде программы. Которая потом выполняется, и создает из этих классов объекты.
    А если вы имели в виду объекты, то их создаётся миллион, и все они "попадают" в другие объекты. Даже при обработке НТТР запроса
    Ответ написан
    9 комментариев
  • Как реализовать зависимость аргумента одного класса от другого класса?

    ipatiev
    @ipatiev
    Потомок старинного рода Ипатьевых-Колотитьевых
    Соглашусь с предыдущими комментаторами, пример СЛИШКОМ высосан из пальца, чтобы по нему давать какие-то рекомендации.
    Но в целом, бак должен быть отдельным объектом в составе машины, а никаким не "аругментом".
    И у него может быть метод consumeFuel(miles)
    Который может принимать длину дороги в качестве аргумента, и уменьшать количество топлива в соответствии с заложенной формулой.

    Car.Tank.fillMax()
    print Car.Tank.getLevel()
    Car.Тank. consumeFuel(Road.length)
    print Car.Tank.getLevel()

    По поводу последнего варианта - всегда исходите из здравого смысла. Может ли быть дорога частью автомобиля? Нет, это очевидная бессмыслица. Дорога может быть частью поездки. Как и автомобиль. Вот в рамках объекта Trip они вполне могут взаимодействовать друг с другом.

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

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

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

    В одном соглашусь с ними: никогда не нужно делать два дела одновременно: и решать конкретную задачу, и учить новые концепции.
    Поэтому лучшим вариантом написания кода будет такой: сначала наговнякать процедурный, но рабочий вариант.
    А потом заняться его рефакторингом: посмотреть, какие можно использовать классы, как они могут взаимодействовать.
    Ответ написан
    4 комментария
  • Как правильно распределять ответственность между классами?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Сервис - это часть модели. Класс, который реализует некую бизнес-логику, не связанную напрямую с хранилищем.
    Репозиторий - часть модели, класс, который содержит методы для работы с хранилищем.
    Стор - это какая-то местечковая приблуда. Из битрикса небось? Либо какие-то академические измышления, вот как в соседнем ответе. Вроде человек что-то пишет, но яснее ничего не становится.
    Маппер имеет много значений. Если речь про Data Mapper, то это разделение самих данных и их отображения в БД.

    Читать стандартно - Дядюшка Боб Мартин и Мартин Фаулер. Конкретно по РНР - Зандстра.
    Ответ написан
    2 комментария
  • Метод класса вписывается в класс который осуществляет действие или в класс над которым осуществляют действие?

    ipatiev
    @ipatiev
    Потомок старинного рода Ипатьевых-Колотитьевых
    Уфф, 5 раз прочитал, пока понял. Второе.

    Метод класса вписывается в класс который над которым осуществляют действие.
    Собственно, определение класса - это "данные и методы для работы с ними". С ними. А не с данными чужих объектов.

    Сущность Модератор в данном случае вообще не нужна, достаточно свойства в сущности Пользователь (кстати, откуда вы все берете этих "участников"? Участник бывает у события, а не у сущности. Вы же не говорите "участник 5А класса?")

    Связей здесь должно быть значительно меньше.
    Если модератор не упоминается ни в опросе, ни в добавленной книге, то и связи между ними никакой нет.
    В текущей схеме должна быть только одна связь: опроса с прошедшим его пользователем. Одним из свойств опроса будет экземпляр класса Пользователь. Причем это касается класса РезультатОпроса, которого нет на диаграмме. А он должен быть. И вот он будет связан с классом Опрос.

    Чего здесь не хватает - это интерфейса. Классов-контроллеров. Которые собственно эти методы и вызывают.
    Какой-нибудь BookController.add(), который принимает, скажем, два объекта, Пользователь и Инпут, и проверяет, является ли пользователь модератором. И если да, то создает объект Book, и используя данные из инпута, вызывает Book.add()
    И то же самое с опросами

    Еще по-хорошему должен быть BookRepository. Потому что если книга умеет сама себя добавлять в хранилище, то это считается плохим тоном. Должен быть отдельный объект Хранилище Книг, в котором и будут методы добавления и поиска.
    Ответ написан
    6 комментариев
  • Как понять что я готов к ООП?

    ipatiev
    @ipatiev
    Потомок старинного рода Ипатьевых-Колотитьевых
    Если спрашиваете, то не готовы.

    В этом вопросе важно понимать, что изучив всего лишь объектный синтаксис, вы столь же готовы писать ООП, сколь готовы профессионально играть в шахматы, изучив как ходят фигуры.
    Главное в ООП - не синтаксис. А взаимодействие объектов. Композиция, наследование, инкапсуляция. Связность, связанность. Это все довольно сложно, и по усилиям, которые требуется затратить на обучение, запредельно отличается от изучения основ процедурного программирования.

    Так что просто запаситесь терпением, набирайтесь опыта в программировании, и постепенно разбирайтесь с ООП - сначала на примере чужих классов и книжек. Мартин, Фаулер - вот это вот всё.
    Ответ написан
    Комментировать
  • Может кто-нибудь дать реальную задачу на которой можно применить ООП?

    ipatiev
    @ipatiev
    Потомок старинного рода Ипатьевых-Колотитьевых
    Это хороший вопрос, но однозначного ответа на него нет.

    Во-первых, надо понимать, что писать свои классы и использовать готовые - это две очень большие разницы.
    От современного программиста чаще всего требуется второе. И это не требует особого понимания ООП.

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

    В-третьих, все реальные примеры настолько замороченные, что по ним разобраться совершенно нереально. А упрощенные ничего толком не показывают. Только синтаксис.

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

    Это пример самой базовой и очень распространенной задачи - как сделать работу с SQL менее занудной и гарантированно безопасной. И начинать надо именно с таких задач, не замахиваясь на приложения целиком.

    Если говорить о приложении целиком, то стоит попробовать написать что-то примере фреймворка Symfony - это как раз даст понимание того, как ООП применяется на уровне приложения.
    Ответ написан
  • Есть ли смысл реализовывать интерфейс абстрактным классом?

    ipatiev
    @ipatiev
    Потомок старинного рода Ипатьевых-Колотитьевых
    Я это понимаю так:
    Интерфейс - это публичный контракт. Информация для посторонних, для тех кто будет с классом взаимодействовать.
    Абстрактный класс - это внутренняя кухня, прототип для реализации.
    То есть одно другому никак не противоречит. Даже если абстрактный класс и интерфейс будут сильно похожи.
    Ответ написан
    Комментировать
  • Как кастомизировать ексепшены в общем методе?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Первый вариант - это однозначно глупость. Какое-то совсем уже натягивание процедурного подхода на ООП.
    Второй какой-то невнятный, про него ничего нельзя сказать толком.

    В общем случае, как правильно написал sl0 в комментарии, кидать надо ОДИН тип исключения, но с разными сообщениями.

    В данном конкретном случае, поскольку речь идёт о валидации, то никакое исключение в принципе кидаться не должно. Исключение кидается в случае, если функция не может выполнить свою работу. Но для функции checkParam() невалидный параметр НЕ ЯВЛЯЕТСЯ исключительной ситуацией. Потому что роль этой функции - как раз и определить валидность переданного параметра. В случае неверных данных функция должна записать сообщение об ошибке во внутренний массив ошибок класса-валидатора и вернуть false.
    spoiler

    Исключение в случае неверных параметров может кидать например функция сохранения в БД. Потому что её задача - сохранить переданные данные, а не валидировать их. И в случае, если функция не может сохранить данные - она должна кинуть исключение, в том числе из-за невалидности данных.
    Ответ написан
    9 комментариев
  • Какие основные понятия в ООП?

    ipatiev
    @ipatiev
    Потомок старинного рода Ипатьевых-Колотитьевых
    • Инкапсуляция - это самое простое. В объекте лежат данные и методы для работы с ними (причём данными могут быть и другие объекты. см. Композиция). Самое главное в инкапсуляции - не переборщить. Инкапсулировать только то, что относится одной конкретной задаче. Всё остальное делегировать другим объектам (см. Композиция).
    • Наследование - это тоже самое простое и самое опасное. Захотел добавить новый функционал к уже существующему классу - унаследовался, дописал методик - и в путь! А потом исходный класс поменяли, и он стал ломать поведение унаследованного. Лучше всего взять себе за правило наследоваться только от абстрактных классов. А поведение менять с помощью свойств-объектов других классов (см. Композиция).
    • Полиморфизм. Один метод - поведение разное. Проще всего достигается за счёт использования свойств-объектов других классов (см. Композиция).
    • Композиция - это самое интересное. Объект действует не сам по себе, а с помощью свойств-объектов, которые передаются в конструктор при создании объекта. Например, у нас есть класс Модели, который должен уметь делать КРУД. А точнее сам по себе он содержит только данные, а в качестве зависимости в него передаётся объект для работы с БД, имеющий собственно эти самые методы create(), read(), update() и delete(). И вот этот объект может быть как инстансом класса, работающего, например, с Mysql, а может быть - работающего с Редисом. И теперь, в зависимости от наших потребностей, одна и та же модель может сохраняться как в Редис, так и в РСУБД. Без изменения и единственной строчки кода!
    Ответ написан
    1 комментарий
  • Как продолжить цепь запросов в php?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Если отбросить всю не относящуюся к вопросу чепуху, типа каких-то "внутренних запросов"(?!), то ответ сводится цепочке вызовов.
    Ответ написан
    Комментировать
  • Насколько целесообразно разбиение на функции и классы?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Это нормально.
    Ну то есть про конкретный код нельзя сказать, не видя его, но " выносить каждые пару строк в отдельную функцию" - это нормально. Куда нормальнее, чем писать всю тысячу в одной.

    Рекомендую найти видео "Как писать код, который понравится вашим тестам" с первой PHPRussia. Там очень доходчиво рассказывается про то, почему надо делать простые методы. Не только для тестов. А ещё и для повторного использования и для упрощения поддержки кода. Когда надо прочитать основную логику модуля, то даже сокращение в два раза (по одному методу вместо двух строк кода) уже сильно облегчает задачу.
    А если уж даже тест понял, что делает ваш код, то человек поймёт и подавно :)

    Как вам написали выше - все дело в именах методов. Если они информативные, то есть если конструкция
    if ($this->function_7($a) && $this->function_8($b))
    читается как связный английскй текст, то mission accomplished! Функции выполняют свою роль: легче всего читать код, которого нет. Вместо того, чтобы разбирать, что делает тот или иной код, можно просто прочитать небольшой текст на английском.
    Ответ написан
    4 комментария