Паттерн Strategy

11.09.2012

Уже очень давно я хотел детальнее разобраться в паттернах проектирования и написать серию статей по ним. И вот момент настал ;)

Во многих книгах видел описание паттернов, но лучшей, по моему мнению, является книга Паттерны проектирования, из последнего поста о книгах, и уже после или вдогонку к ней я бы советовал читать Php:Объекты, шаблоны, методики проектирования и Банду четырёх.Изучать паттерны мы будем на PHP, в размере — не меньшем 1 паттерна в неделю.

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

Полный код можно посмотреть здесь - https://github.com/neo-classic/Strategy-Pattern

01. Задача + Теория паттерна

Осваивать шаблоны проектирования будем на примере, и наверное самый популярный пример, в котором можно применить Strategy — персонажи игры.

Итак, создаем класс Character, который будет включать следующий метод: attack(). У каждого типа персонажа своя атака и свой тип оружия — своё поведение. Как это реализовать? А что будет если мы захотим добавить новый тип оружия в нашу игру? Ок, сейчас разберемся.Пускай нам нужны King (sword), Warrior (axe) и Wizard (blunt) – в скобках написан тип оружия.

Что же предлагает нам паттерн Стратегия:

  1. Выделять в отдельные классы то, что изменяется (инкапсулировать);
  2. Отделять предпочтение композиции перед наследованием (создание нового класса с использованием включения уже существующих);
  3. Программировать на уровне интерфейсов (в нашем случае все классы оружия наследуются от общего интерфейса);
  4. Изменять поведение персонажей во время выполнения программы.

В нашем случае поведение — это выбор оружия, его мы и будим инкапсулировать в отдельные классы.

Ошибочное проектирование: внутри наших классов King, Warrior и Wizard – делать несколько if-else конструкций для проверки типа оружия.

02. Суперкласс

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

abstract class AbstractCharacter {
    abstract public function talk();

    public function setWeapon($weapon) {
        $this->weapon = $weapon;
    }

    public function attack() {
        if(isset($this->weapon)) {
            return $this->weapon->damage();
        } else {
            throw new Exception('You must use setWeapon() function.');
        }
    }
}

Свойство $weapon будет содержать объект типа оружия. Метод setWeapon() как раз используется для изменения поведения во время выполнения. Метод attack() возвращает силу атаки оружием (в нашем случае число, указанное внутри класса оружия, можно усложнить — если добавить собственную атаку персонажа и прибавлять к ней атаку оружия).

03. Определяем поведение

Напомню — один из принципов качественного кода — программирование на уровне интерфейса, для этого создадим интерфейс для оружия:

interface WeaponBehavior {
    public function damage();
}

Далее используем этот интерфейс для нашего оружия (покажу на примере класса Sword):

class Sword implements WeaponBehavior {
    public function damage() {
        return 150;
    }
}

По аналогии создаем оставшиеся классы оружия — Axe и Blunt.

04. Реализуем персонажей

На примере King:

class King extends AbstractCharacter {
    public function __constuct() {
        $this->weapon = new Sword();
    }

    public function talk() {
        echo "I\'m the King!";
    }
}

Как видим в конструкторе включаем класс Sword в качестве оружия для персонажа King. Так же в суперклассе определен абстрактный метод talk() - мы обязаны его переопределить везде, где наследуем суперкласс.

05. Тестируем

Создаем index.php со следующим кодом:

include('characters/AbstractCharacter.php');
include('characters/King.php');
include('characters/Warrior.php');
include('characters/Wizard.php');

include('weapon/WeaponBehavior.php');
include('weapon/Sword.php');
include('weapon/Axe.php');
include('weapon/Blunt.php');

$king = new King();
$king->talk();
$king->setWeapon(new Axe());
echo $king->attack();

Проверяем вывод в браузере, всё должно быть отлично. Комментим, если что не ясно или не так, а следующим паттерном будет Фабрика.

Полный код смотрим здесь - https://github.com/neo-classic/Strategy-Pattern

blog comments powered by Disqus
Наверх