Шаблон проектирования Стратегия
Есть компания, которая разрабатывает имитаторы машин. В игре представлен город, в котором ездят разные виды машин. Проектировщики создали один суперкласс, на основе которого объявляются конкретные виды машин.
Другими словами, у нас есть суперкласс Car и много дочерних класса, которые реализуют конкретные типы машин.
Руководство компании решает, что лучше было бы, если машины не просто ездили сами по себе, а еще и возили какой-то полезный груз.
Программист недолго думая находит решение. Он решает добавить метод carry() в суперкласс Car и этот метод будет унаследован всеми производными классами.
Мы можем просто добавить в суперкласс Car метод carry():
Вроде бы хорошее решение, ведь теперь все дочерние классы наследуют этот метод, и все они могут возить людей. Радостный своим быстрым и удачным решением программист приходит к друзьям выпить по бутылке пива. Но как только он открывает бутылку, раздается звонок от начальника. Начальник рассказывает, что в программе начали возить людей сломанные машины, макеты машин и даже игрушечные машины.
Что же произошло? У нас есть дочерний класс BrokenCar, который не может возить людей, потому что это сломанная машина. Теперь после изменения программиста у нас и сломанные машины без колес возят людей.
Программист думает, может переопределить метод carry() в классе BrokenCar?
Сделав пустое переопределение, мы можем решить задачу. А так же нам придется сделать так же во всех дочерних классах, которые НЕ могут возить.
Но что произойдет, когда в программу добавятся деревянные машины, которые не должны ни ездить, ни возить и не сигналить?
К продукту постоянно добавляются новый функционал и новые дочерние классы. Во всех классах нам нужно будет искать и если нужно переопределять метод carry(). Нам нужен более легкий способ заставлять возить только некоторых машин.
И тут программист предлагает такое решение. Исключить из суперкласса Car метод carry() и ride() и создать интерфейсы Carryable, Rideable. Только те машины, которые должны возить людей реализуют интерфейс и содержат метод carry().
Но тут возникает такая проблема: Программисту придется вносить маленькие изменения во всех дочерних классах, которые могут возить людей. Интерфейсы PHP не имею реализации. Если когда-нибудь потребуется изменить аспект поведений, придется искать и изменять его во всех субклассах.
Мы знаем, что методы ride(), carry() – часть класса Car, изменяющиеся в зависимости от субкласса. Нам нужно отделить эти аспекты от класса Car. Чтобы это сделать, мы выносим оба метода за пределы класса Car и создаем новые классы для представления каждого аспекта.
Такая архитектура позволяет использовать поведение carry() и ride() в других типах объектов, потому что это поведение не скрывается в классах Car.
Кроме того, мы можем добавлять новые аспекты поведения без изменения существующих классов поведения, и без последствий для классов Car, использующих существующее поведение.
Интеграция с классом Car, можно поделить на несколько шагов.
- Сначала в класс Car включаются две переменные экземпляров rideBehavior и carryBehavior, объявленные с типом интерфейса (а не с типом конкретного класса реализации). Каждый объект на стадии выполнения присваивает этим переменным полиморфные значения, соответствующие конкретному типу поведения.
- Методы ride(), carry() удаляются из класса Car (и всех субклассов), потому что это поведение перемещается в классы RideBehavior и CarryBehavior.
- Вместо методов ride(), carry() в класс Car добавляем performRide(), performCarry().
Пример кода performRide():
abstract class Car {
public $rideBehavior; // ссылка на реализацию интерфейса RideBehavior;
public function performRide(){
//объект Car делегирует поведение объекту, на который ссылается $rideBehavior
$this->rideBehavior->ride();
}
}
Пример кода:
abstract class Car {
public $rideBehavior;
public $carryBehavior;
public function __construct() {}
public abstract function display();
public function beep(){}
public function setRideBehavior(RideBehavior $rb){
$this->rideBehavior = $rb;
}
public function setCarryBehavior(CarryBehavior $cb){
$this->carryBehavior = $cb;
}
public function performRide(){
$this->rideBehavior->ride();
}
public function performCarry(){
$this->carryBehavior->carry();
}
}
class PickupCar extends Car {
public function __construct() {
parent::__construct();
$this->setCarryBehavior(new CarryHuman());
$this->setRideBehavior(new RideByMan());
}
public function display() {
echo 'This is pickup!';
}
}
class ToyCar extends Car {
public function __construct() {
parent::__construct();
$this->setCarryBehavior(new CarryNoWay());
$this->setRideBehavior(new RideByMan());
}
public function display() {
echo 'This is toy!';
}
}
interface RideBehavior {
public function ride();
}
class RideByMan implements RideBehavior {
public function ride(){
echo 'Ride by Man!';
}
}
class RideNoWay implements RideBehavior {
public function ride(){
echo 'Ride impossible!';
}
}
interface CarryBehavior {
public function carry();
}
class CarryHuman implements CarryBehavior {
public function carry() {
echo 'It carries human!';
}
}
class CarryNoWay implements CarryBehavior {
public function carry(){
echo 'It can\'t carry anybody!';
}
}
$mycar = new PickupCar();
$mycar->performCarry();
$mycar->performRide();
$mycar->setCarryBehavior(new CarryNoWay);
$mycar->performCarry();
$yourCar = new ToyCar();
$yourCar->performCarry();
$yourCar->performRide();
Общая картина будет выглядеть так:
Под конец определение паттерна проектирования Стратегия.
Паттерн Стратегия определяет семейство алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. Он позволяет модифицировать алгоритмы независимо от их использования на стороне клиента.