Паттерны проектирования и особенности их применения.

Design Patterns: Elements of Reusable Object-Oriented Software

Book by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm

Patterns of Enterprise Application Architecture

Book by Martin Fowler

PHP Objects, Patterns, and Practice, Second Edition

Book by Matt Zandstra

https://martinfowler.com/

Паттерны на практике

Composite over inheritance (или принцип повторного использования композитов) - это принцип, согласно которому классы должны обеспечивать полиморфное поведение и повторное использование кода по их составу (путем размещения экземпляров других классов, которые реализуют желаемую функциональность), а не наследования от базы или родителя класс

Проектирование интерфейсов

Нет поддержки множественного наследования

Композиты удобнее тестировать

С использованием композитов использование паттернов приходит само собой  

Композиты лучше сохраняют энкапсулацию

Композит легко заменить

Про существование паттернов легко забыть как только начинаешь применять композицию

Strategy

Chain of Responsibility

Decorator

Delegate

FactoryMethod

Template

Proxy

Observer

Visitor

Factory

Builder

Unit of Work

Adapter

/**
 * Class DelegatingCalculator
 */
class RewardCalculator implements RewardCalculatorInterface
{
    /**
     * Array of calculators indexed by account type
     *
     * @var RewardCalculatorInterface[]
     */
    private $calculators;

    /**
     * Calculator to use if none matches to specific one
     *
     * @var RewardCalculatorInterface
     */
    private $defaultCalculator;

    /**
     * DelegatingCalculator constructor.
     *
     * @param RewardCalculatorInterface[] $calculators       Specific calculators indexed by account types
     * @param RewardCalculatorInterface   $defaultCalculator Fallback calculator
     */
    public function __construct(array $calculators, RewardCalculatorInterface $defaultCalculator)
    {
        $this->calculators       = $calculators;
        $this->defaultCalculator = $defaultCalculator;
    }

    /**
     * @inheritDoc
     */
    public function calculateRewards(Reward $reward): Reward
    {
        $certainCalculator = $this->calculatorFor($reward->dealInfo->getDeal())
        
        return $certainCalculator->calculateReward($reward);
    }

    /**
     * Selects calculator for given deal
     *
     * @param Deal $deal Deal for getting calculator
     *
     * @return RewardCalculatorInterface
     */
    private function calculatorFor(Deal $deal): AbstractCalculator
    {
        $type = $deal->getUser()->getAccountType();

        return $this->calculators[$type] ?? $this->defaultCalculator;
    }
}

Strategy

class ExcludingCalculator implements RewardCalculatorInterface
{
    /**
     * Nested calculator
     *
     * @var RewardCalculatorInterface
     */
    private $nested;

    /**
     * AccountTypeExcludingCalculator constructor.
     *
     * @param RewardCalculatorInterface $nested               Nested reward calculator
     */
    public function __construct(RewardCalculatorInterface $nested)
    {
        $this->nested = $nested;
    }

    public function calculateReward(Reward $rewards): Reward
    {
        $reward = reset(iterator_to_array(new RewardPeriodExcludingIterator([$rewards])));

        return $this->nested->calculateReward($reward);
    }
}

Decorator

class ExcludingCalculator implements RewardCalculatorInterface
{
    /**
     * Nested calculator
     *
     * @var RewardCalculatorInterface
     */
    private $nested;

    /**
     * AccountTypeExcludingCalculator constructor.
     *
     * @param RewardCalculatorInterface $nested               Nested reward calculator
     */
    public function __construct(RewardCalculatorInterface $nested)
    {
        $this->nested = $nested;
    }

    public function calculateReward(Reward $rewards): Reward
    {
        $reward = reset(iterator_to_array(new RewardPeriodExcludingIterator([$rewards])));

        return $this->nested->calculateReward($reward);
    }
}

Chain of Responsibility

<?xml version="1.0" ?>
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://symfony.com/schema/dic/services"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="alfaforex.partnership.reward_calculator.commission.excluding.bond"
                 public="false"
                  class="Alfaforex\Partnership\Services\RewardCalculator\Commission\ExcludingCalculator\BondsExcludingCalculator">
            <argument type="service" id="alfaforex.partnership.reward_calculator.commission.excluding.customer_status"/>
        </service>

        <service id="alfaforex.partnership.reward_calculator.commission.excluding.customer_status"
                 public="false"
                 class="Alfaforex\Partnership\Services\RewardCalculator\Commission\ExcludingCalculator\CustomerStatusExcludingCalculator">
            <argument type="service" id="alfaforex.partnership.reward_calculator.commission.excluding"/>
        </service>
        <service id="alfaforex.partnership.reward_calculator.commission.excluding"
                 public="false"
                 class="Alfaforex\Partnership\Services\RewardCalculator\Commission\ExcludingCalculator\ExcludingCalculator">
            <argument type="service" id="alfaforex.partnership.reward_calculator.commission"/>
        </service>
    </services>
</container>

Настроим цепочку руками

<?xml version="1.0" ?>
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://symfony.com/schema/dic/services"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="alfaforex.partnership.reward_calculator.commission.excluding.bond"
                 public="false"
                  class="Alfaforex\Partnership\Services\RewardCalculator\Commission\ExcludingCalculator\BondsExcludingCalculator"
                 decorates="alfaforex.partnership.reward_calculator.commission"
                 decoration-inner-name="alfaforex.partnership.reward_calculator.commission.excluding.bond.inner"
                 decoration-priority="700">
            <argument type="service" id="alfaforex.partnership.reward_calculator.commission.excluding.bond.inner"/>
        </service>

        <service id="alfaforex.partnership.reward_calculator.commission.excluding.customer_status"
                 public="false"
                 class="Alfaforex\Partnership\Services\RewardCalculator\Commission\ExcludingCalculator\CustomerStatusExcludingCalculator"
                 decorates="alfaforex.partnership.reward_calculator.commission"
                 decoration-inner-name="alfaforex.partnership.reward_calculator.commission.excluding.customer_status.inner"
                 decoration-priority="800">
            <argument type="service" id="alfaforex.partnership.reward_calculator.commission.excluding.customer_status.inner"/>
        </service>
        <service id="alfaforex.partnership.reward_calculator.commission.excluding"
                 public="false"
                 class="Alfaforex\Partnership\Services\RewardCalculator\Commission\ExcludingCalculator\ExcludingCalculator"
                 decorates="alfaforex.partnership.reward_calculator.commission"
                 decoration-inner-name="alfaforex.partnership.reward_calculator.commission.excluding.multi_condition.inner"
                 decoration-priority="900">
            <argument type="service" id="alfaforex.partnership.reward_calculator.commission.excluding.multi_condition.inner"/>
        </service>
    </services>
</container>

... или задекорируем из коробки

$calculator = new BondsExcludingCalculator(
            new CustomerStatusExcludingCalculator(
                new ExcludingCalculator(
                    new CommissionCalculator()
                )
            )
        );

Получится что-то вроде этого

class ExcludingCalculator implements RewardCalculatorInterface
{
    /**
     * Nested calculator
     *
     * @var RewardCalculatorInterface
     */
    private $calculators;

    /**
     * AccountTypeExcludingCalculator constructor.
     *
     * @param RewardCalculatorInterface[] $calculators Calculators
     */
    public function __construct(array $calculators)
    {
        $this->calculators = $calculators;
    }

    public function calculateReward(Reward $reward): Reward
    {
        foreach($this->calculators as $calculator) {
            $reward = $calculator->calculate($reward);
        }

        return $reward
    }
}

Composite

Когда  использовать наследование ?

  • Иерархия наследования представляет собой " - это есть", а не "имеет это" зависимость

  • Вам нужно использовать код из базового класса

  • Нужно иметь возможность модифицировать базовый метод

  • Отсутствие необходимости глубокой иерархии классов

  • Нужно иметь возможность изменить поведение целого класса объектов

Класс является частным случаем базового класса

Абтракции

 Абстракции

 Абстракции

Made with Slides.com