Ответы пользователя по тегу ООП
  • Какой из этих подходов в ООП лучше и как они называются?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    с самого начала у нас есть более-менее абстрактный класс


    Так не должно быть. "абстрактные" классы это способ устранения дублирования, нам больше важны интерфейсы объектов и полиморфизм. Но конкретно в рассматриваемом примере нам важна только инкапсуляция.

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

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

    Если мы хотим создать объект с каким-то состоянием по умолчанию - мы берем первый вариант (либо как в примере прописывваем прямо как значение свойства, либо сэтим в конструкторе).

    Если мы хотим создать "пустой" объект и уже потом определить его состояние - мы выбираем второй вариант. Как правило этот способ создает много побочных эффектов если не контролировать его, да и обычно это делают не потому что так правильно а потому что подругому не умеют (типичный пример - сущности доктрины. Многие просто не представляю себе как с ними работать без сеттеров).

    Если же мы хотим создать объект но он не может быть пустым, объект должен требовать начальное значение в момент создания. Тогда мы просто передаем значение в конструктор. Без этого значения (или с неправильным) мы не сможем создать объект. К примеру если у пользователя обязательно должен быть email и пароль имеет смысл "заставлять" разработчиков явно передавать их в конструктор объекта. Это эдакая вариация.

    А теперь попробуем первый и второй подходы вместе + третий кейс с обязательными параметрами:

    class User {
         private $id;
         private $email;
         private $password;
    
         public function __construct($email, $password) 
         {
               // это ваш первый пример только без наследования, оно не нужно
               // в нашей задаче идентификатор пораждается
               // при создании объекта самим объектом, состояние по умолчанию
               $this->id = Uuid::uuid4(); 
    
               // мы требуем входящие данные что бы задать начальное состояние
               $this->email = $email;
               $this->password = $password;
         }
    
         // мутация состояния, второй вариант.
         public function changePassword($password) 
         {
                $this->password = $password;
         }
    }


    То есть вся разница только в том, откуда приходят данные для формирование объектом своего состояния. Изнутри (первый случай) или снаружи (второй случай). Оба подхода вполне себе можно использовать вместе.

    Ну и да - зачем в вашем примере наследование и нейкий "абстрактный" класс - не понятно. Наследование это не принцип, это механизм для достижения полиморфизма подтипов. С ним нужно быть осторожно, особенно если с полиморфизмом не разобраться сначала.
    Ответ написан
    Комментировать
  • Будет ли данный функционал избыточен для класса?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    не будет ли это излишним?


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

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

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

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Нет.

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

    Параметрический полиморфизм. Это когда мы можем написать один код, с одним набором имен, которые работает с разными типами аргументов. Пример - шаблоны из C++ или дженерики в Java. То есть "имена" методов одинаковые, потому что они в одном экземпляре. Реализация одна, одно поведение. А вот аргументы могут отличаться.

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

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

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

    Ну и еще есть одно серьезное ограничение. Если мы хотим заменить в системе объект какого-то типа на объект подтипа (грубо говоря наследника), то система не должна сломаться. То есть "другое" поведение нашего подтипа должно быть совместимо в плане интерфейса с базовым типом. Об этом можно почитать загуглил "Принцип подстановки Барбары Лисков".

    Ad-hoc полиморфизм - это пожалуй самый интересный вид полиморфизма с которым можно долго холиварить. По сути при этом виде полиморфизма, у нас одинаковые имена, а поведение зависит от входящих аргументов. Пример - перегрузка методов в C++. Интересен этот вид полиморфизма в основном тем, что он не является "настоящим".

    При динамической системе типов не требуется никаких дополнительных возможностей вроде той же перегрузки методов для достижения ad-hoc полиморфизма. Тупо кидаем что хотим в функцию, а там уже if-ами рагребаем или же приводим к какому-то одному варианту. Отдельные конструкции нужны в языках со статической системой типов. То есть нам нужно еще на этапе компиляции кода знать какие именно типы могут приходить в наши методы, и в зависимости от оных вызывать тот или иной код.

    Среди PHP-разработчиков немало тех, кто мечтает увидеть в этом языке с динамической системой типов честную перегрузку методов как например в Java или C++. Просто так, потому что if-ы это плохо и лучше уж пусть они будут неявные на уровне компилятора/рантайма.

    Полиморфизм с приведением типов - еще один вид "не настоящего" полиморфизма. Мы "эмулируем" полиморфизм за счет того, что на уровне рантайма языка происходят касты действительного в желаемое. Например в PHP мы можем выставить у функции тайпхинтинг string, и можем внутри иметь одно и то же поведение для всех входящих аргументов. Передать же в качестве аргумента мы можем все что можно скастить в строку.

    Собственно, это очень похоже на параметрический полиморфизм по смыслу, но далеко не так гибко и не дает того контроля за системой.
    Ответ написан
    Комментировать
  • Когда может понадобиться сохранять объект класса внутри себя?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Только при описании структур данных древовидных. Но не в конструкторе, ибо в этом случае мы получаем рекурсивный вызов конструктора объекта.
    Ответ написан
    Комментировать
  • Как правильно спроектировать классы?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Это называется двусторонней many-to-one связью, и как правила вам стоит избегать оных если вот без них совсем неудобно. Скажем сотруднику совсем не обязательно знать о том что он принадлежит какой-то компании. А потому от свойства company мы можем легко и просто отказаться, если конечно у нас нет какой-то специфичной логики.

    class Employe {
        constructor(firstName) {
            this.firtsName = firstName;
        }
    }
    
    class Company {
        constructor(name, employes){
            this.name = name;
            this.employes = employes;     
        }
    
        rename(name) {
           this.name = name;
        }
    
        addEmploye(employe) {
             this.eployes.push(employe);
        }
    }
    Ответ написан
  • Одинаковые куски кода в разных классах допустимо или нубство?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    нубство. Выносите общее в методы, в общие зависимости. Почитайте про DRY.

    Дублирование позволительно если это... ну тупой код и от дублирования никуда не деться. Но у вас тут какие-то выражения, логика, поведение дублируется. А это уже нубство.
    Ответ написан
    3 комментария
  • JavaScript ООП, запутался?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    шаблон создания повторного использования кода, то есть ооп


    Вы запутались. ООП не про повторное использование кода, функции/процедуры больше за это отвечают. Объекты отвечают за сокрытие состояния.

    Автор писал что в свойство prototype вообще не нужно лезть.

    Изнутри инстанса объекта - да. Когда ты описываешь новый тип объектов и хочешь отнеследоваться - то надо указать прототип объекта.

    То ли пока я учил JavaScript что то поменялось в этом плане

    Чуть поменялось, вышел ES2015, еще в прошлом году. Теперь все чуть проще и работа с прототипами объектов при наследовании/определении новых объектов стала чуть более прозрачной и удобной. И про createObject/defineProperties можно помнить только что бы понимать как все работает.
    Ответ написан
  • Когда использовать методы когда объекты php?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    где их использование оправданно?


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

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

    В целом же в PHP можно удобно использовать только ОО подходы (при построении архитектуры приложений, это не значит что мы не можем применять идеи функционального программирования, и не путаем это с процедурным подходом), потому все сводится к простым штукам типа инкапсуляции, полиморфизму, принципам SOLID и GRASP. По умолчанию все должно быть объектами и все должно иметь свой тип. Далее уже можно упрощать в зависимости от требований.
    Ответ написан
    Комментировать
  • Зачем нужны абстрактные классы (PHP)?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    типичный юзкейс для абстрактный классов - базовая реализация, уменьшение дублирования кода при появлении общего решения и т.д. Например у вас есть интерфейс с несколькими методами, и только 2-3 из них реализуются по разному. Выгодно вынести общую реализацию в базовый абстрактный класс.
    Ответ написан
    1 комментарий
  • Когда появилась первая ORM(Object-relational mapping)?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Насколько я могу судить "первопроходцами" решения проблема мэппинга данных из базы на объекты... стали те у кого эта проблема возникла впервые. А именно у чуваков из Oracle, которые предложили одну из первых ORM - TOPLink. Сначала для Smalltalk в начале 90-х и потом для Java.
    Ответ написан
  • ООП. Как правильно организовать связь классов?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    что бы нужен был абстрактный класс у вас должно быть две реализации одного интерфейса у которых проявляется дублирование.
    Ответ написан
    3 комментария
  • Конструктор в массиве объекта. Как реализовать?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    да, нужно вызвать конструктор, конструкция
    Curier[] crs = new Curier[n];

    создает массив ссылок нужного типа. Грубо говоря

    Curier[] crs = new Curier[2];

    равносильно

    Curier crs = null, 
           crs2 = null;
    Ответ написан
    1 комментарий
  • Куда делегировать функции, которые являются частью контроллера, но слишком велики, чтоб оставлять их там?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    организуйте сервисный слой приложения.
    Ответ написан
    Комментировать
  • Как лучше создавать класс в javascript?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    в чём же разница в new 'класс' и object.create 'класс',


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

    создавать классы, в прототипном или функциональном стиле ?

    в js нет классов. В любом случае - лучше методы объявлять в прототипе.

    https://babeljs.io/docs/learn-es2015/#classes
    Ответ написан
    Комментировать
  • Где лучше обучиться и найти документацию?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    PDO

    PDO? PHP Data Objects?

    php.net/manual/en/book.pdo.php

    хотя в целом лучше взять готовый dbal, например doctrine/dbal

    семинары по ООП

    Есть книжки, есть лекции. Например есть неплохие лекции от Сергея Немчинского (пример), у него конечно по Java но суть от этого не меняется. Более того, почти вся стоящая литература ориентирована на Java, но спроэцировать эти знания на PHP проблемы не составляет.

    И с какого PHP фреймворка лучше начать обучаться?

    Ни с какого, сначала освойте PHP и ООП на базовом уровне. Потом освойте git, composer. Потом попробуйте тесты пописать (TDD попрактиковать еще можно, хорошо для обучения), всякие там phpunit/phpspec/peridot/etc. А уже потом можно... уже брать какой-нибудь фреймворк.
    Ответ написан
    1 комментарий
  • Замещение, как основной способ создания собственных элементов объектов, как перефразировать вопрос?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    наследование? виртуальные методы?
    Ответ написан
    Комментировать
  • Что такое статическое наследование в ООП?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    статическое наследование - наследование статических членов классов.
    Ответ написан
    Комментировать
  • Что такое static в ООП php?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Для этого вам надо понимать в чем разница между классами и объектами. Вот есть методы и свойства объектов а есть методы и свойства классов. Последние как раз статические свойства и методы. Отсюда и все особенности их работы и возможные варианты использования. Класс у нас в системе может быть только один, а экземпляров этого класса (объектов) много.

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

    $foo = Singleton::instance();
    $bar = AbstractFactory::create('bar');
    $buz = Buz::fromArray([
        'many' => 'arguments', 'Buz' => 'has', 'private' => 'constructor'
    ]);


    В PHP статику еще любят применять как замену обычным функциям в силу того, что для классов автозагрузка у нас есть, а для функций нету. Не сказать что это сильно хорошо, я бы даже сказал что это плохо. Учитывая что сейчас есть composer а благодаря opcache оверхэда от подключения для каждого запроса файла особо и нет. В целом лучше стараться избегать использования статики или во всяком случае делать в статических методах хоть сколько нибудь сложные вещи. И лучше всегда ограничиваться только случаями для порождения объектов, тут статика выглядит логично.

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

    class Foo {
        public static function createWithSelf() {
             // равносильно new Foo();
             return new self();
        }
        public static function createWithStatic() {
             // а тут мы пока не знаем кто такой этот static
             $foo = new static();
        }
    }
    
    class Bar extends Foo {}
    
    $foo = Bar::createWithSelf(); // тут будет экземпляр Foo
    $bar = Bar::createWithStatic(); // тут будет экземпляр Bar
    Ответ написан
    1 комментарий
  • Где используется паттерн "Репозиторий"?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Паттерн репозиторий используется для изоляции логики хранения данных. Например:

    interface UserRepository {
        function getUser($id);
        function getUsersWhichSatisfyMyCustomBuisnessRulecs(BuisnessRules $rules);
        function saveUser(User $user);
    }
    
    class InMemoryUserRepository implements UserRepository {
        private $users = [];
    
        function getUser($id) {
             return isset($this->users[$id]) ? 
                 $this->users[$id] : null;
        }
    
        function getUsersWhichSatisfyMyCustomBuisnessRulecs(BuisnessRules $rules) {
             return array_filter($this->users, function (User $user) use ($rules) {
                   return $user->isSatisfyRule($rules->getSomeRule());
             }
        }
        function saveUser(User $user) {
            $this->users[$user->getId()] = $user;
        }
    }


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

    - что нет жесткой привязки к реализации. Вы можете использовать внутри репозитория plain sql, data mapper, active record, ассоциативные массивы, файлы... ну вы поняли. Главное интерфейс заимплементить.

    - репозиторий позволяет вам построить архитектуру приложения не связывая все с логикой хранения данных. Таким образом мы организуем слабую связанность системы и придерживаемся принципа единой ответственности.

    martinfowler.com/eaaCatalog/repository.html
    Ответ написан
    5 комментариев
  • Как написать хороший роутер или где взять готовый?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Или у этого роутера есть шанс на жизнь?

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

    Как нормально написать все это дело ?

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

    Что посоветуете ?

    https://packagist.org/search/?q=router и в частности https://github.com/nikic/FastRoute
    ну и чтиво на ночь: nikic.github.io/2014/02/18/Fast-request-routing-us...
    Ответ написан