Абстрактная фабрика и примеси в PHP (Часть 1)

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

Мне хотелось бы описать связь между различными практиками программирования, которые я встречал в своих проектах. Если опыт будет удачным, то попробую сделать цикл подобных постов.

#Джуниор-разработчик и игра
Допустим, мы хотим написать браузерную игру, в которой «добро» борется со «злом». Пусть у обеих сторон есть пехота, лучники и осадные орудия. Для начала посадим за IDE джуниора. Через некоторое время мы, скорее всего, увидим следуюшие классы:

class GoodInfantry{}
class GoodArcher{}
class GoodSiegeWeapons{}

class EvilInfantry{}
class EvilArcher{}
class EvilSiegeWeapons{}

#Опытный разработчик и абстрактная фабрика

Придет опытный разработчик (ОР) и скажет, что это же класика для шаблона проектирования «абстрактная фабрика». Шаблоны проектирования разбирали уже тысячи раз на различных языках, но все же я хочу сделать статью самодостаточной и опишу еще разок. Если вы знаете «банду четырех», как свои пять пальцев, то перейдите к следующему подзаголовку.

ОР знает, что «добрая» сторона обязательно будет иметь свои особенности, отличные от «злой». При этом так же понятно, что пехота с обоих сторон так же будет иметь нечто общее. Таким образом каждый юнит фактически должен унаследовать какие-то свойства от своей стороны, а какие-то от своего типа.

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

Второй момент, который стоит учесть, связан с тем, что юнитов в игре будет много и создавать их будет не программист, а игрок. То есть программист не сможет просто прописать в коде нужное количество раз создание, допустим, пехотинца. Вместо этого он должен учесть создание пехотинца какой-то функцией. А, вспомнив, что мы находимся в концепции ООП, скорее методом какого-то более общего объекта. Допустим, что юнитов создает у нас «фабрика света» на «доброй» стороне и «фабрика тьмы» на «злой». Светлая фабрика будет выглядеть так:

Вспомним так же, что все пехотинцы будут наследовать класс Infantry. И подумаем о том, что фабрики, вероятно, будут иметь одинаковый набор методов, а значит хорошо бы им унаследовать единый абстрактный класс. Такой подход гарантирует нам, что ОР, создав абстрактные классы может идти курить и доверить написание реализации джуниору, ведь любая ошибка в реализации будет сразу видна. То, что у нас получилось, фактически, и есть паттерн Абстрактная фабрика. стрелки без подписи «создает» обозначают наследование.

Такая структура — пример хорошего кода для общего случая создания групп связанных обьектов. То есть всегда, когда у нас есть необходимость создавать экземпляры классов не в ручном режиме и когда у нас есть группы связанных объектов (в нашем случае все светлые и все темные связаны) паттерн подходит. Удобно то, что он раширяется, как горизонтально (можно добавлять еще стороны, кроме света и тьмы), так и вертикально без переписывания кода многократно.

<?php

abstract class Infantry {
    //общие для всей пехоты методы
    abstract public function Attack();
    abstract public function Defend();
}
abstract class Archer {
    abstract public function Shoot();
}
abstract class SiegeWeapons {
    abstract public function Fire();
}

class GoodInfantry extends Infantry {
    //Спецэфическая реализация для доброго пехотинца
    public function Attack();
    public function Defend();
}
class GoodArcher extends Archer{
    public function Shoot();
}
class GoodSiegeWeapons extends SiegeWeapons{
    public function Fire();
}

class EvilInfantry extends Infantry{
    //Спецэфическая реализация для злого пехотинца
    public function Attack();
    public function Defend();
}
class EvilArcher extends Archer{
    public function Shoot();
}
class EvilSiegeWeapons extends SiegeWeapons{
    public function Fire();
}

abstract class AbstractFactory
{
    abstract public function createInfantry();
    abstract public function createArcher();
    abstract public function createSiegeWeapons();
}

class GoodFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new GoodInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new GoodArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new GoodSiegeWeapons();
    }
}

class EvilFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new EvilInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new EvilArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new EvilSiegeWeapons();
    }
}

Причем же тут примеси? Продолжение следует.

Share
Send
Pin
Popular