Шаблон проектирования Стратегия

Есть компания, которая разрабатывает имитаторы машин. В игре представлен город, в котором ездят разные виды машин. Проектировщики создали один суперкласс, на основе которого объявляются конкретные виды машин.

Другими словами, у нас есть суперкласс Car и много дочерних класса, которые реализуют конкретные типы машин.

Руководство компании решает, что лучше было бы, если машины не просто ездили сами по себе, а еще и возили какой-то полезный груз.

Программист недолго думая находит решение. Он решает добавить метод carry() в суперкласс Car и этот метод будет унаследован всеми производными классами.

Мы можем просто добавить в суперкласс Car метод carry():

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

Что же произошло? У нас есть дочерний класс BrokenCar, который не может возить людей, потому что это сломанная машина. Теперь после изменения программиста  у нас и сломанные машины без колес возят людей.

Программист думает, может переопределить метод carry() в классе BrokenCar?

Сделав пустое переопределение, мы можем решить задачу.  А так же нам придется сделать так же во всех  дочерних классах, которые НЕ могут возить.

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

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

И тут программист предлагает такое решение. Исключить из суперкласса Car метод carry() и ride() и создать интерфейсы CarryableRideable. Только те машины, которые должны возить людей реализуют интерфейс и содержат метод carry().

Но тут возникает такая проблема: Программисту придется вносить маленькие изменения во всех дочерних классах, которые могут возить людей.  Интерфейсы PHP не имею реализации. Если когда-нибудь потребуется изменить аспект поведений, придется искать и изменять его во всех субклассах.

 

Мы знаем, что методы ride()carry() – часть класса Car, изменяющиеся в зависимости от субкласса. Нам нужно отделить эти аспекты от класса Car. Чтобы это сделать, мы выносим оба метода за пределы класса Car и создаем новые классы для представления каждого аспекта. 

Такая архитектура позволяет использовать поведение carry() и ride() в других типах объектов, потому что это поведение не скрывается в классах Car.

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

Интеграция с классом Car, можно поделить на несколько шагов.

  1. Сначала в класс Car включаются две переменные экземпляров rideBehavior и carryBehavior, объявленные с типом интерфейса (а не с типом конкретного класса реализации). Каждый объект на стадии выполнения присваивает этим переменным полиморфные значения, соответствующие конкретному типу поведения.
  2. Методы ride()carry() удаляются из класса Car (и всех субклассов), потому что это поведение перемещается в классы RideBehavior и CarryBehavior.
  3. Вместо методов 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();

Общая картина будет выглядеть так:

Под конец определение паттерна проектирования Стратегия.

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

комментарии (0)