Паттерны проектирования и особенности их применения.
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
Когда использовать наследование ?
-
Иерархия наследования представляет собой " - это есть", а не "имеет это" зависимость
-
Вам нужно использовать код из базового класса
-
Нужно иметь возможность модифицировать базовый метод
-
Отсутствие необходимости глубокой иерархии классов
-
Нужно иметь возможность изменить поведение целого класса объектов
Класс является частным случаем базового класса

Абтракции
Абстракции
Абстракции

Паттерны проектирования
By faecie
Паттерны проектирования
- 231