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