Ответы пользователя по тегу ООП
  • Что такое событие в ООП?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Пример:
    User имеет поведение:
    - changeName(newName) — меняем имя
    - activate() — активируем (меняется статус с првоеркой, не заблокрирвоан ли к примеру или еще что-то)
    - changeEmail(newEmail)

    Синопсис:
    Все изменения можем смотреть у объекта (например геттеры) — вызывать состояние объекта и сравнивать со старым, предварительно зная старый

    Но можем испускать события — User копит таковые в некотором внутрнеем массиве domainEvents, все важные изменения там и пишутся
    Потом просто выше по коду попросить у объекта "дай-ка события, которые есть,
    Например методом raiseEvents() мы получим события об изменении состояний/активации/смены имени/смена токенов

    Архитектурно это может быть не метод raiseEvents(), а какой-нибудь observable (если у нас реактивная модель) и просто подписку повесить с обработчиком стрима, а можем просто вызвать метод получения
    далее с этими событиями работать

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

    События могут быть не доменные (не как выше пример с доменным объектом), а события приложения/сервисной службы... Например есть юзкейс о регистрации, в приложении у вас есть эвент-дисптечер, при регистрации пользователя после корректного завершения через диспетчер кидаете событие и приложение как-то его обрабатывает внутри, вызывая обработчики события — это позволяет понизить связаность в системе: вам не нужно явно вызывать логику по месту регистрации пользователя, а просто зарегистрировать обработчик (указать, на какие события ему реагировать)

    Есть событие, есть обработчик, нужно новое поведение навесить — регистрируете еще обработчик и делаете в ней логику, которая нужна, сам код регистрации пользователя остется не тронутым (вспоминанем вясокую связанность, так вот — мы не завязаны на код этой самой регистрации)

    События в этом случае являются средством управления IoC (инверсия контроля кода)
    Ответ написан
    Комментировать
  • Зачем задавать приватный модификатор доступа для свойств класса?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    У вас очень логичный вопрос на счет сеттера: сеттер полностью уничтожает задумку с инкапсуляцией. Правильно это называется "семантическое нарушение инкапсуляции" — то есть как-бы мы инкапсулировали, но по факту назад открыли прямую запись в свойство напрямую. Все доводы "ну мы можем в сеттере валидировать" не корректны

    Правильным способом будет не использовать сеттеры. Вот моя статья на эту тему:
    https://habr.com/ru/post/469323/

    Более подробно: каждый класс нужно проектирвовать так, чтобы данные внутри были максимально связаны. Например каждый метод работать должен в хорошем случае с максимальным числом полей класса, тогда у него высокий cohesion... В тоже время снаружи наужно работать с максимальн омалым числом метода, тогда будет низкая связанность (coupling). Это пара принципов из GRASP.
    Когда вы делаете сеттеры и геттеры, то у вас данные внутри между собой почти никак не взаимодействут: с геттером работают снаружи, с сеттером работаю снаружи — весь класс нараспашку, а в нем в 100% случаев появляются данные, которые вместе не должны находиться и никак не связаны — анрушена и абстракция и инвариант и много чего еще...

    Про инвариант отдельно: например есть платеж, у него есть значение, с которым платеж инициирвоан (initValue), есть значение холда (holdAmount) и есть значение чарджа на списание (chargeAmount)
    Когда вы работаете с платежом, контролируя ивнариант в самом классе, то ваш каждый метод првоеряет др значения и позволяет перейти к др состоянию... методов будет 2-3, все инкапсулировано и безопасно.

    Например:
    class Payment {
         pub func charge(amount int) void {
              if (this.holdAmount < amount && this.initAmount < amount) {
                   throw new PaymentException('Unavailable charge amount')
              }
    
               if (this.status === PaymentStatus:finish) {
                   throw new PaymentException('Payment already fisnished')
              }
    
              this.chargeAmount = amount
              this.holdAmount -= amount
              this.status = PaymentStatus:finish
         }
    }


    Тут в одном методе полные проверки и класс сам контролирует все состояние внутри, также соблюдается закон Деметры. У даннго кода высокий cohesion (из GRASP), тк внутри идет плотная работа с внутр данными (значит они корректно тут закроекны) и низкий coupling ( с теми данными для данного кейса только один метод работы, все внутри)

    Когда вы раскроете сеттерами и геттерами, то ваше состояние становится непредсказуемо и полагается только на то, что снаружи точно подумали об инварианте (нет)
    Ответ написан
    23 комментария
  • Почему я не могу объявить аргумент метода типа потомка от аргумента родительского класса?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Ковариантность и контравариантность ¶
    Официальная документация

    Если коротко: сужать тип у метода наследника можно для возвращаемого значения, а для аргумента можно расширять. И только так, дабы соблюсти Liskov Substitution Principle. В TypeScript это самая популярная проблема, куда разрабов языка постоянно тыкают носом... тк программы в рантайме падают из-за возможности расширять/сужать как тип параметра, так и возвращаемого значения
    Ответ написан
    Комментировать
  • Как получить даты всех дочерних объектов бинарного дерева?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    У вас корректный код:
    sandbox.onlinephpfunctions.com/code/00ed97cf62c568...

    <?php
    
    class Node{
      private $arr = [];
      
      public function getChildren($node)
        {
    
            if (!empty($node->data)){
                array_push($this->arr, $node->data);
            }
    
            if (!empty($node->left)){
                $this->getChildren($node->left);
            }
    
            if (!empty($node->right)){
                $this->getChildren($node->right);
            }
    
            return $this->arr;
        }  
    }
    
    $node =  createNode(
        6 . '_base', 
        createNode(7 . '_right'),
        createNode(8 . '_left',
            createNode(81 . '_left'),
            createNode(82 . '_right',
               createNode(821 . '_right', 
                    null,  
                    createNode(824 . '_left') 
               )
              
            )
        )
    );
    
    function createNode($data, $right = null, $left = null) {
        $obj = new stdClass();
        $obj->data  = $data;
        $obj->right  = $right;
        $obj->left  = $left;
        return $obj;
    }
        
    var_dump((new Node)->getChildren($node));
    
    // [
    //  "6_base"
    //  "8_left"
    //  "82_right"
    //  "821_right"
    //  "824_left"
    //  "81_left"
    //  "7_right"
    // ]
    Ответ написан
  • Можно ли использовать сервисы в rich моделях?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    а? Если нет и нужно все равно писать абсолютно всю логику в обьекте модели, то тогда он станет просто god обьектом на 100500 строчек

    Потому что вы все делаете наоборот!

    Пишите по бизнес-процессу ваши UseCase, это некоторый хэндлер к примеру
    Пример
    final class HandleCheckOutShoppingCart
    {
        public function __construct(Carts $carts, PaymentGateway $gateway)
        {
            $this->carts   = $carts;
            $this->gateway = $gateway;
        }
    
        public function __invoke(CheckOutShoppingCart $command) : void
        {
            $shoppingCart = $this->carts->get($command->shoppingCart());
    
            $payment = $this->gateway->captureCharge($command->charge());
    
            $shoppingCart->checkOut($payment);
        }
    }


    Потом у вас вырисовываются границы сущности, и в сущностях уже инварианты, которые контролирует эта сущность. Никаких god object
    Ответ написан
    5 комментариев
  • Как реализовать систему ролей на Java?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Сделайте отдельную сущность типа Subscribe, или Order и свяхите со своей ролью по id,
    тогда будет ясно — есть ли заказ (и его подходящий статус) или нет

    Тогда у вас будут все читатели, но у некоторых активная подписка/заказ
    Ответ написан
  • Скажите, а шаблон проектирования (для PHP), где фичи для приложения выносятся в отдельные модули - имеет какое-то название?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    где фичи для приложения выносятся в отдельные модули

    В вопросе уже овтет :):):)

    Package By Feature

    Вот можете посмотреть на примере PHP-проекта от Удальцова Валентина, хорошая попытка (с привязкой по времени):
    https://www.youtube.com/watch?v=2iPNz3p5Xiw&t=3633s

    как разбить приложение на переиспользуемые модули-фичи

    А вот тут сомнения по моему ответу, тк цель обратная — понизить связанность, тогда как переиспользуемость — повышение ее в некотором роде
    Может быть вы сейчас про DRY? Don't Repeat Yourself?
    Ответ написан
    1 комментарий
  • Как обратиться к свойству объекта заведомо не зная этого свойства?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Семантика у задачи плохая очень (каталог в каталоге, у каталога товар? один? :)
    Но в целом вот на коленке как достать значения вложенной глубины

    <?php
    
    $catalog = new stdClass();
    $catalog->product = new stdClass();
    $catalog->product->item = 'Short';
    $catalog->product->model = 'Adidas';
    
    $catalogPaths = ['product.item', 'product.model'];
    
    
    function extractValuesByPath(object $obj, array $paths) {
        return array_reduce($paths, function(array $values, string $pattern) use($obj) {
            $values[] = extractPathValue($obj, $pattern);
            return $values;
        }, []);
    }
    
    function extractPathValue($obj, $pattern) {
        $paths = explode('.', $pattern);
        
        foreach($paths as $path) {
            if (!property_exists($obj, $path)) {
                // Можно нулл вернуть или к пример `undefined $path`
                throw new InvalidArgumentException('Нет такого свойства во входящих данных!');
            }
        
            $obj = $obj->$path;
        }
        
        return $obj;
    }
    
    $res = extractValuesByPath($catalog, $catalogPaths);
    var_dump($res);
    // [
    //       'Short',
    //       'Adidas',
    // ]
    Ответ написан
  • Можете привести пример принципа открытости/закрытости?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Декоратор — декорирующее поведение вынесено в др класс с др названием в рамках одной абстракции (соблюдается интерфейс). Это соблюдается open closed principle.

    Но если Декорирующее поведение помещают в сам исходный класс с if/else — это нарушение принципа.

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

    Декоратор — не единственный верный способ соблюсти, но хорошо иллюстрирует, например отличным способом может быть вычленение какого-то поведения в отдельную абстракцию
    Ответ написан
    Комментировать
  • Как действительно поможет ООП в реальной программе?

    Maksclub
    @Maksclub
    maksfedorov.ru
    ООП упрощает код, логику и понимание...
    Но только в случае подготовленного человека. Соответственно пока нет осознанности в происходящем и опыта, то ясен пень — будет сложно и не понятно

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

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

    Без ООП — абстракции не будут иметь четких границ и смысл будет в разобранном состоянии собираться из крупиц по коду и данным, но и такой, процедурный подход, имеет преимущества
    Ответ написан
    Комментировать
  • Когда применяем паттерн Стратегия, а когда Декоратор?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Стратегия = полиморфизм, то есть мы завязаны на некий интерфейс, а какая реализация — нам не важно. Это история про зависимости. Ну например почтальон отдает пенсию бабушкам (любым, какой бабушке именно — зависит от стратегии, КОТОРАЯ НЕ СВЯЗАНА с модификацией конкретной бабушки:)

    Декоратор, это про добавить функционал в рамках одного интерфейса, тут вообще не рассматривается вопрос каких-либо отношений (к примеру бабушки и почтальона), тут рассматривается — бабушка в шубе или бабушка с загаром или бабушка на коляске, все та же бабушка, но "обернутая" неким поведением :) Главное что бабушка всегда остается быть той самой для всех бабушкой. То есть это не противопоставление — ни в начале ни в середине мы не завязываемся на дополнительное поведение бабушки у почтальона. Бабушка и все, а какая именно — зависит от стратегии разноса (например по названию улицы). Если выйдет к нему "декорированная" бабушка-качок — пенсию он даст ей также, как и не качку, тк она для него всего лишь некий субъект/абстракция, главное чтобы возраст и ФИО сошлись.

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

    Кое-где не корректные аналогии, и в аналогии стратегия есть бабушка, но в целом для понимания норм и не критично :)
    Ответ написан
    Комментировать
  • Является ли это (делегирование) нарушением принципа единственной ответственности – SRP?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Если в объекте Order будет зависимость ProductList, то не нарушает SRP, тк этот объект (судя по названию) VO и состояние этого объекта необходимо для работы Order.

    Но вызывает вопросы способ работы с ним:
    создавать отдельный метод getProductList, в котором будет инициализирован класс

    Почему не в конструкторе объекта Order? Или почему не из зависимости извне (DI) вообще получать товары?
    class Order
    {
         private ProductStorageInterface $products;
    
         public function __construct(ProductStorageInterface $products) 
         {
               $this->products = $products;
         }
         
         public function refundProduct(Product $product): void
         {
               // логика возврата товара 
               // и соответственно изменение  состава $this->products
         }
    }


    И товары могут подгрузиться из памяти, БД или АПИ внешней.
    Тут заодно и принцип инверсии зависимости

    UPD: Не называйте метод getProductList(), это ужасно! Называйте как есть: createProductList() или loadProductList(), ну в зависимости от логики.
    get -- слишком общее слово и совсем не говорит о том, что происходит. Если все кругом будет сервисами (service) с методами get, то вам весело будет жить...
    Ответ написан
    4 комментария
  • Зачем нужна абстракция в ООП?

    Maksclub
    @Maksclub
    maksfedorov.ru
    В Python нет интерфейсов, потому приходится так объявлять абстракцию без поведения.
    Зачем нужна абстракция -- затем, чтобы код не завязывать на конкретный объект, а завязать на некоторые границы поведения (что и есть абстракция) -- в данном случае на некоторый SUbject
    class Observer(ABC):
        @abstractmethod
        def update(self, subject: Subject) -> None:
            """
            Получить обновление от субъекта.
            """
            pass

    Теперь Observer (все его потомки) в параметрах метода update() принимают ЛЮБОЙ объекта типа Subject, то есть код завязан на абстракцию, а не реализацию этих subjects и вообще ничего не знает о их названиях и индивидуальном поведении, знает только, что прилетит любая абстракция типа Subject....
    Ответ написан
    Комментировать
  • Чем представлена абстракция (принцип ООП) в Java?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Абстракция — ключевое явление в ООП

    Сам класс — уже абстракция чего-то, когда я создаю класс Product, то это абстракция некоторых объектов моей системы, что их отличает от всего внутреннего мира системы уже как минимум названием (и потом интерфейсом и поведением, описанным в классе).

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

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

    Наследование, инкапсуляция и полиморфизм — ключевые способы для создания абстракции.
    • Полимофризм — создаем или интерфейс или абстрактный класс, что очертит границы абстракции и уже в конкретных реализациях абстракцию "материализуем". Или создаем некий класс, который работает с любым типом, но строго указанным при создании объекта (параметрический полиморфизм)
    • Наследование — похожее поведение у ряда классов выносим в абстракцию (родительский класс, абстрактный класс)
    • Инкапсуляция — очищаем абстракцию, отдав наружу только публичный интерфйес (границы абстракции), все остальное (не относящиеся к нашей абстракции) прячем
    Ответ написан
    Комментировать
  • Как выполнить несколько методов одним разом python?

    Maksclub
    @Maksclub
    maksfedorov.ru
    пара способов

    ЦЕПОЧКА ВЫЗОВОВ
    В коде вы привели пример, как хотели бы использовать: caller.hello().world()

    Если хотите, чтобы последний метод возвращал результат, то можно так:
    class Hello(object):
        def __init__(self):
             self.msg = 'Hello'
    
        def hello(self, world):
            self.msg = self.msg + ', ' + world
            return self
    
        def print(self):
            return self.msg
    
    caller = Hello()
    msg = caller.hello('niriter').print()
    print(msg) # Hello, niriter

    Такого рода код используется в разного рода билдерах, когда нужно настроить обьект разным способом по разным условиям (разного рода билдеры, в ОРМ запрос собрать и прочее)

    В вашей библиотеке по работе с html такой способ и используется (наряду со вторым) — вызываете разного рода методы, а возвращает все тот же обьект с разным состоянием и вы всегда можете вызвать новый метод (удалить элемент, добавить, все стереть) и по итогу вызвать получение своего нужного HTML

    ИНКАПСУЛЯЦИЯ

    class Hello(object):
        def __init__(self, msg='Friend'):
             self.msg = msg
    
        def print(self):
            init_msg = self.hello()
            return init_msg + ',  ' + self.msg
        
        def hello(self): # этот метод вызываем изнутри другого метода
            return str("Hello")
    
    caller1 = Hello()
    print(caller1.print()) # Hello, Friend
    
    caller2 = Hello('Maks')
    print(caller2.print()) # Hello, Maks


    Инкапсуляция из мира ООП, наружу предоставляет некий метод, и вызвав его — выполнится некоторое поведение и изменение состояния. Нужна для сокрытия всех деталей и нюансов и оставляет только удобное АПИ для работы.

    В вашей библиотеке по работе с html такой способ и используется (наряду с первым) — прячет сложные алгоритмы извлечения элементов, манипуляции с ними, отдавая назад только удобное АПИ с понятными названиями
    Ответ написан
    3 комментария
  • Что здесь лучше использовать? Простое наследование, интерфейсы, или вообще абстрактный класс?

    Maksclub
    @Maksclub
    maksfedorov.ru
    куча мест где выводится некий Список объявлений

    все они являются Списками, и эти списки по сути отображают одинаковые карточки

    Использовать DTO списка объявлений (или массив), который состоит из списка DTO объявления. Только 1 по сути класс для отображения
    Ответ написан
    6 комментариев
  • Можно ли Композицию и наследование назвать паттерном?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Паттерн — довольно утилитарная вещь, некая четкая последовательность или описание связей, чтобы можно было повторить, легко опознать и донести тот или иной инструмент.

    Композиция — абстрактное понятие, классифицирующая то, что нужно выстроить как-то иначе, нежели наследование. Делегировать и выстроить так, чтобы полиморфизм проявил себя через некоторое число объектов, не связанных в цепочку наследования.
    Наследование —про то, что объекты наследуют интерфейс/состояние, тут нет схемы взаимодействия, она довольно прямая: А от Б отнаследовался и все. Но внутри сокрыто много всего.
    Ответ написан
    Комментировать
  • Dependency Injection Container, попробовал на практике - не понял смысла?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Контейнер
    Главная задача контейнера — переиспользовать сервисы более одного раза, например некий провайдер более чем в 1 месте или некий Sender в 20 местах по всему приложению. И упростить к ним доступ.

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

    Взаимодействие относительно простых объектов/сущностей в рамках одного слоя лучше делать вашим способом — оно логичное и понятное и вообще контейнер будет только мешать, даже больше — не к месту в этой задаче. Это вам в ответе на том форуме и отметили.

    Современные контейнеры предоставляют на самом деле еще ооочень много фишек: автовайринг, когда вам и конфиг писать не нужно, просто в конструкторе написали класс, а контейнер если нашел такой класс, то сам и сконфигурирует (и также учтет его параметры конструктора), то есть разработчику остается работа только с уникальными и не стандартными ситуациями.
    Или дает упрощенные фабрики/билдеры/декораторы.

    Dependency Injection
    И в будущем удобно будет тестировать ?

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

    Главное не объединяйте эти два понятия. Сам паттерн — просто частный случай инверсии зависимости, класс получил зависимость через конструктор/сеттер, и все, он не знает деталей, потому и тестить проще — просто подменил на пустышку.
    А контейнер — средство для работы со сложным приложением, которое внедряет как раз таки, то есть выполняет работу внедрения. Центральное в нем — слово контейнер.

    Несколько точек входа
    По поводу ваших точек входа: несколько точек входи = несколько иницализаций.
    Как-бы они разные по своей сути приложения уже по определению получаются.
    Но вы можете упростить, и вынести инициализацию контейнера в абстракцию — в некий класс App/Kernel и там это делать, а в точке входа инициализировать не контейнер раз за разом, а именно конкретное приложение.
    Ответ написан
    7 комментариев
  • Правильно ли я понимаю суть Интерфейсов в ООП?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Интерфейс — основа полиморфизма.
    Есть один интерфейс, с ним умеет работать некий код. Вуаля, любая реализация этого интерфейса подходит для этого самого кода.

    Пример: есть некий класс заказа Order, он работает с неким отправителем Sender, мы можем жестко "научить" его работать с ним в коде этого Order, а можем просто передавать ему отправителя через конструктор (привет DI), но передавать не сам класс/реализацию, а лишь его абстракцию — интерфейс, то, что будет отправлять, но как — не важно, это будет SenderInterface.

    Теперь мы в Order можем юзать полиморфизм: использовать любой Sender, который реализует этот самый SenderInterface

    Код:
    interface SenderInterface
    {
         public function send(): void {};
    }
    
    class Order
    {
         // тут будет любой, но обязан реализовать SenderInterface
         private $sender;
    
         public function __constuctor(SenderInterface $sender) {
              $this->sender = $sender;
         }
     
         public function save(): void 
        {
            // какой в приватном поле сидит, тот и отправит, 
            // а кто -- текущему объекту все равно, это есть инверсия зависимости 
            // и принцип единственной ответственности, данный класс не отправляет, а поручает
            $this->sender->send();
        }
    }
    
    class MailSender implement SenderInterface
    {
         public function send(): void
         {
               // тут отправка почтой
         }
    }
    
    class TelegramSender implement SenderInterface
    {
         public function send(): void
         {
               // тут отправка телегой
         }
    }
    
    
    // Пример полиморфной работы Order
    $order = new Order(new TelegramSender());
    $order->save(); // тут отправка телеграмом
    
    $order = new Order(new MailSender());
    $order->save(); // тут отправка mail


    Как видите, мы можем добавлять новые сендеры, в них что-то делать, а с ордером будем работать как прежде $order->save() , и вообще его код не трогать

    Дополнение
    Интерфейс есть у любого класса (публичны/приватный), интерфейс наследуется у абстрактного/обычного класса ребенком (кроме того, что наследует поведение), почему полиморфизм иногда (на самом деле ооочень часто) объясняют именно на наследовании одного класса и множественной реализации в виде детей. На самом деле все дело в этом самом интерфейсе.
    Ответ написан
    1 комментарий
  • Какой использовать паттерн проектирования для интеграции c внешним сервисом?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Не складывается все пока в единую структуру

    Глаза боятся, руки делают

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

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

    Простой набросок со Стратегией

    Можно придумать некий интерфейс клиента:
    // Принимает ваши учетные данные
    DeliveryClientInterface::__construct(?string $account = null, ?string $password = null)
    
    // Регистрируем покупателя в сервисе
    DeliveryClientInterface::registerCustomer(DeliveryCustomer $customer): int
    
    // Получаем заказы покупателя в сервисе
    DeliveryClientInterface::getOrders(int $customerId): DeliveryOrder
    
    // Добавляем заказ покупателю
    DeliveryClientInterface::addOrder(int $customerId, DeliveryOrder $order): int
    
    // Оповещение покупателя в сервисе о неком действии, связанной с ним в этом сервисе
    DeliveryClientInterface::notifyCustomer(DeliveryEvent $event): bool


    И научить свой проект работать с таким кодом, через интерфейсы
    // что в конструктор сервиса запихнете, например PochtaClient или PickPointClient,
    // с тем и будете работать
    class DeliveryService
    {
        public function __construct(DeliveryClientInterface $deliveryClient, User $user)
    }
    
    $userOrders = $this->deliveryService->getOrders($user->getUuid());


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

    Далее в клиентском коде подключайте нужный вам клиент через конфиг/контейнер и все будет работать.
    Ответ написан
    Комментировать