Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Образцы проектирования PHP. Часть 1. Порождающие

Anna | 29.05.2014 | нет комментариев

Тема заезженная до дыр, не спорю… Возможно, для опытных разработчиков моя статья будет немного, чем пригодна. Я бы рекомендовал её к прочтению тем, кто только начал осмысливать, что его коду чего-то не хватает, и что он созрел для вникания в это далёкое представление – «паттерны». По себе помню, что достаточно длинное время я путался в образцах, изредка даже не понимая, чем один отличается от иного. Именно данный факт стал основой для моей статьи. Примеры в ней не будут реальными. Они будут абстрактными и максимально примитивными. Впрочем я постараюсь все примеры удерживать в цельном контексте, Дабы дозволено было наглядно видеть различия их применения в одной и той же обстановки. Я не буду нагружать классы лишним функционалом, Дабы дозволено было осознать, какая именно часть кода имеет непосредственное отношение к образцу. Основными героями примеров станут Factory (фабрика) и Product (продукт, изготавливаемый этой фабрикой). Возьмём это отношение за отправную точку. Допустимо, в некоторых примерах это будет не дюже целесообразно, но но дюже наглядно…

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

Порождающие образцы проектирования

С вашего позволения, я не буду пересказывать на сотый раз, кто такие, эти порождающие шаблоны… Я легко оставлю тут ссылку на википедию. Краткость – сестра дара. Следственно сразу предлагаю примеры.

Реестр (Registry)

Хотелось бы начать с этого образца. Он немножко выбивается из всеобщего ряда, потому что не является порождающим, но в последующем нам понадобится его познание. Выходит, реестр – это хэш, доступ к данным у которого осуществляется через статические способы:

Пример 1

<?php
/**
 * Реестр
 */
class Product
{

    /**
     * @var mixed[]
     */
    protected static $data = array();

    /**
     * Добавляет значение в реестр
     *
     * @param string $key
     * @param mixed $value
     * @return void
     */
    public static function set($key, $value)
    {
        self::$data[$key] = $value;
    }

    /**
     * Возвращает значение из реестра по ключу
     *
     * @param string $key
     * @return mixed
     */
    public static function get($key)
    {
        return isset(self::$data[$key]) ? self::$data[$key] : null;
    }

    /**
     * Удаляет значение из реестра по ключу
     *
     * @param string $key
     * @return void
     */
    final public static function removeProduct($key)
    {
        if (isset(self::$data[$key])) {
            unset(self::$data[$key]);
        }
    }
}

/*
 * =====================================
 *           USING OF REGISTRY
 * =====================================
 */

Product::set('name', 'First product');

print_r(Product::get('name'));
// First product

Зачастую дозволено встретить реестры, реализующие интерфейсы ArrayAccess и/или Iterator, но на мой взор, это лишнее. Основное использование реестра – в качестве безвредной замены глобальным переменным.

Пул объектов (Object pool)

Данный образец, по сути, является частным случаем реестра. Пул объектов – это хэш, в тот, что дозволено складывать инициализированные объекты и доставать их оттуда при необходимости:

Пример 2

<?php
/**
 * Пул объектов
 */
class Factory
{

    /**
     * @var Product[]
     */
    protected static $products = array();

    /**
     * Добавляет продукт в пул
     *
     * @param Product $product
     * @return void
     */
    public static function pushProduct(Product $product)
    {
        self::$products[$product->getId()] = $product;
    }

    /**
     * Возвращает продукт из пула
     *
     * @param integer|string $id - идентификатор продукта
     * @return Product $product
     */
    public static function getProduct($id)
    {
        return isset(self::$products[$id]) ? self::$products[$id] : null;
    }

    /**
     * Удаляет продукт из пула
     *
     * @param integer|string $id - идентификаторпродукта
     * @return void
     */
    public static function removeProduct($id)
    {
        if (isset(self::$products[$id])) {
            unset(self::$products[$id]);
        }
    }
}

class Product
{

    /**
     * @var integer|string
     */
    protected $id;

    public function __construct($id) {
        $this->id = $id;
    }

    /**
     * @return integer|string
     */
    public function getId()
    {
        return $this->id;
    }
}

/*
 * =====================================
 *         USING OF OBJECT POOL
 * =====================================
 */

Factory::pushProduct(new Product('first'));
Factory::pushProduct(new Product('second'));

print_r(Factory::getProduct('first')->getId());
// first
print_r(Factory::getProduct('second')->getId());
// second

 

Одиночка (Singleton)

Вероятно, один из самых знаменитых образцов. Как правило, его все запоминают первым. А ещё при поиске работы про него дюже любят спрашивать на собеседованиях. Вот самый примитивный пример:

Пример 3

<?php
/**
 * Одиночка
 */
final class Product
{

    /**
     * @var self
     */
    private static $instance;

    /**
     * @var mixed
     */
    public $a;

    /**
     * Возвращает экземпляр себя
     *
     * @return self
     */
    public static function getInstance()
    {
        if (!isset(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Конструктор закрыт
     */
    private function __construct()
    {
    }

    /**
     * Клонирование запрещено
     */
    private function __clone()
    {
    }

    /**
     * Сериализация запрещена
     */
    private function __sleep()
    {
    }

    /**
     * Десериализация запрещена
     */
    private function __wakeup()
    {
    }
}

/*
 * =====================================
 *           USING OF SINGLETON
 * =====================================
 */

$firstProduct = Product::getInstance();
$secondProduct = Product::getInstance();

$firstProduct->a = 1;
$secondProduct->a = 2;

print_r($firstProduct->a);
// 2
print_r($secondProduct->a);
// 2

Правило синглтона примитивен, как пять копеек. Для того, Дабы обеспечить существование только одного экземпляра класса Product, мы закрыли все волшебные способы для создания экземпляра класса, клонирования и сериализации. Исключительный допустимый метод получить объект – воспользоваться статическим способом Product::getInstance(). При первом обращении класс сам сделает экземпляр себя и положит его в статическое качество Product::$instance. При последующих обращениях, в рамках выполнения скрипта, способ будет нам возвращать тот же, ранее сделанный, экземпляр класса.

Я добавил в класс открытое качество $a, Дабы продемонстрировать работу одиночки. В данном примере дозволено увидеть, что и $firstProduct, и $secondProduct – есть ни что иное, как ссылка на один и тот же объект.

Пул одиночек (Multiton)

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

Пример 4.1

<?php
/**
 * Всеобщий интерфейс пула одиночек
 */
abstract class FactoryAbstract
{

    /**
     * @var array
     */
    protected static $instances = array();

    /**
     * Возвращает экземпляр класса, из которого вызван
     *
     * @return static
     */
    public static function getInstance()
    {
        $className = static::getClassName();
        if (!isset(self::$instances[$className])) {
            self::$instances[$className] = new $className();
        }
        return self::$instances[$className];
    }

    /**
     * Удаляет экземпляр класса, из которого вызван
     *
     * @return void
     */
    public static function removeInstance()
    {
        $className = static::getClassName();
        if (isset(self::$instances[$className])) {
            unset(self::$instances[$className]);
        }
    }

    /**
     * Возвращает имя экземпляра класса
     *
     * @return string
     */
    final protected static function getClassName()
    {
        return get_called_class();
    }

    /**
     * Конструктор закрыт
     */
    protected function __construct()
    {
    }

    /**
     * Клонирование запрещен«Одиночество» (Singletonitis), тот, что как раз заключается в неуместном применении синглтонов. Так для чего же нам данный образец? Самый распространённый пример – соединение с базой данных, которое создаётся один раз и применяется на протяжении работы скрипта. А ещё во многих фреймворках реестр делают одиночкой и применяют его, как объект, а не как класс со статическими способами.

Фабричный способ (Factory method)


А сейчас предлагаю немножко понизить градус и вновь возвратиться к истокам. Возможен, мы знаем, что бывают фабрики, изготавливающие какой-то свой продукт. Нам не значимо, как именно фабрика делает данный продукт, но мы знаем, что у всякий фабрики есть один многофункциональный метод попросить его:

Пример 5

<?php
/**
 * Фабрика
 */
interface Factory
{

    /**
     * Возвращает продукт
     *
     * @return Product
     */
    public function getProduct();
}

/**
 * Продукт
 */
interface Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName();
}

/**
 * Первая фабрика
 */
class FirstFactory implements Factory
{

    /**
     * Возвращает продукт
     *
     * @return Product
     */
    public function getProduct()
    {
        return new FirstProduct();
    }
}

/**
 * Вторая фабрика
 */
class SecondFactory implements Factory
{

    /**
     * Возвращает продукт
     *
     * @return Product
     */
    public function getProduct()
    {
        return new FirstProduct();
    }
}

/**
 * 1-й продукт
 */
class FirstProduct implements Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName()
    {
        return 'The first product';
    }
}

/**
 * 2-й продукт
 */
class SecondProduct implements Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName()
    {
        return 'Second product';
    }
}

/*
 * =====================================
 *        USING OF FACTORY METHOD
 * =====================================
 */

$factory = new FirstFactory();
$firstProduct = $factory->getProduct();
$factory = new SecondFactory();
$secondProduct = $factory->getProduct();

print_r($firstProduct->getName());
// The first product
print_r($secondProduct->getName());
// Second product

 В данном примере способ getProduct() является фабричным.

Абстрактная фабрика (Abstract Factory)


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

Пример 6

<?php
/**
 * Какой-нибудь файл конфигурации
 */
class Config
{
    public static $factory = 1;
}

/**
 * Какой-то продукт
 */
interface Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName();
}

/**
 * Абстрактная фабрика
 */
abstract class AbstractFactory
{

    /**
     * Возвращает фабрику
     *
     * @return AbstractFactory - дочерний объект
     * @throws Exception
     */
    public static function getFactory()
    {
        switch (Config::$factory) {
            case 1:
                return new FirstFactory();
            case 2:
                return new SecondFactory();
        }
        throw new Exception('Bad config');
    }

    /**
     * Возвращает продукт
     *
     * @return Product
     */
    abstract public function getProduct();
}

/*
 * =====================================
 *             FIRST FAMILY
 * =====================================
 */

class FirstFactory extends AbstractFactory
{

    /**
     * Возвращает продукт
     *
     * @return Product
     */
    public function getProduct()
    {
        return new FirstProduct();
    }
}

/**
 * Продукт первой фабрики
 */
class FirstProduct implements Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName()
    {
        return 'The product from the first factory';
    }
}

/*
 * =====================================
 *             SECOND FAMILY
 * =====================================
 */

class SecondFactory extends AbstractFactory
{

    /**
     * Возвращает продукт
*
     * @return Product
     */
    public function getProduct()
    {
        return new SecondProduct();
    }
}

/**
 * Продукт 2-й фабрики
 */
class SecondProduct implements Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName()
    {
        return 'The product from second factory';
    }
}

/*
 * =====================================
 *       USING OF ABSTRACT FACTORY
 * =====================================
 */

$firstProduct = AbstractFactory::getFactory()->getProduct();
Config::$factory = 2;
$secondProduct = AbstractFactory::getFactory()->getProduct();

print_r($firstProduct->getName());
// The first product from the first factory
print_r($secondProduct->getName());
// Second product from second factory

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

Отложенная инициализация (Lazy Initialization)


 А вот вам ещё одна увлекательная обстановка. Представьте, что у вас есть фабрика, но вы не знаете, какая часть её функционала вам понадобится, а какая – нет. В таких случаях нужные операции выполнятся только если они необходимы и только один раз:

Пример 7

<?php
/**
 * Какой-то продукт
 */
interface Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName();
}

class Factory
{

    /**
     * @var Product
     */
    protected $firstProduct;

    /**
     * @var Product
     */
    protected $secondProduct;

    /**
     * Возвращает продукт
     *
     * @return Product
     */
    public function getFirstProduct()
    {

        if (!$this->firstProduct) {
            $this->firstProduct = new FirstProduct();
        }
        return $this->firstProduct;
    }

    /**
     * Возвращает продукт
     *
     * @return Product
     */
    public function getSecondProduct()
    {

        if (!$this->secondProduct) {
            $this->secondProduct = new SecondProduct();
        }
        return $this->secondProduct;
    }
}

/**
 * 1-й продукт
 */
class FirstProduct implements Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName()
    {
        return 'The first product';
    }
}

/**
 * 2-й продукт
 */
class SecondProduct implements Product
{

    /**
     * Возвращает наименование продукта
     *
     * @return string
     */
    public function getName()
    {
        return 'Second product';
    }
}

/*
 * =====================================
 *      USING OF LAZY INITIALIZATION
 * =====================================
 */

$factory = new Factory();

print_r($factory->getFirstProduct()->getName());
// The first product
print_r($factory->getSecondProduct()->getName());
// Second product
print_r($factory->getFirstProduct()->getName());
// The first product

 При первом вызове способа, фабрика создаёт объект и сберегает его в себя. При повторном вызове – возвращает теснее готовый объект. Если бы мы не вызвали способ, объект бы не создался совсем. Признаю, в данном примере немного смысла. Тут применение этого образца не оправдано. Я легко хотел показать его толк. А сейчас представьте, что создание объекта требует трудных вычислений, многократных обращений к базе данных, да и источников кушает массу. Крайне отличный причина обратить внимание на данный образец.

Прототип (Prototype)


 Некоторые объекты доводится создавать неоднократно. Есть толк сэкономить на их инициализации, исключительно, если инициализация требует времени и источников. Прототип – это предварительно инициализированный и сохранённый объект. В случае необходимости он клонируется:

Пример 8

<?php
/**
 * Какой-то продукт
 */
interface Product
{
}

/**
 * Какая-то фабрика
 */
class Factory
{

    /**
     * @var Product
     */
    private $product;

    /**
     * @param Product $product
     */
    public function __construct(Product $product)
    {
        $this->product = $product;
    }

    /**
     * Возвращает новейший продукт путём клонирования
     *
     * @return Product
     */
    public function getProduct()
    {
        return clone $this->product;
    }
}

/**
 * Продукт
 */
class SomeProduct implements Product
{
    public $name;
}

/*
 * =====================================
 *          USING OF PROTOTYPE
 * =====================================
 */

$prototypeFactory = new Factory(new SomeProduct());

$firstProduct = $prototypeFactory->getProduct();
$firstProduct->name = 'The first product';

$secondProduct = $prototypeFactory->getProduct();
$secondProduct->name = 'Second product';

print_r($firstProduct->name);
// The first product
print_r($secondProduct->name);
// Second product

 Как видно из примера мы сотворили два никак не связанных объекта.

Строитель (Builder)


 Ну и конечный на сегодня образец – строитель. Он пригоден, когда мы хотим инкапсулировать создание трудного объекта. Мы легко расскажем фабрике, какому строителю доверить создание продукта:

Пример 9

<?php
/**
 * Какой-то продукт
 */
class Product
{

    /**
     * @var string
     */
    private $name;

    /**
     * @param string $name
     */
    public function setName($name) {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function getName() {
        return $this->name;
    }
}

/**
 * Какая-то фабрика
 */
class Factory
{

    /**
     * @var Builder
     */
    private $builder;

    /**
     * @param Builder $builder
     */
    public function __construct(Builder $builder)
    {
        $this->builder = $builder;
        $this->builder->buildProduct();
    }

    /**
     * Возвращает сделанный продукт
     *
     * @return Product
     */
    public function getProduct()
    {
        return $this->builder->getProduct();
    }
}

/**
 * Какой-то строитель
 */
abstract class Builder
{

    /**
     * @var Product
     */
    protected $product;

    /**
     * Возвращает сделанный продукт
     *
     * @return Product
     */
    final public function getProduct()
    {
        return $this->product;
    }

    /**
     * Создаёт продукт
     *
     * @return void
     */
    public function buildProduct()
    {
        $this->product = new Product();
    }
}

/**
 * 1-й строитель
 */
class FirstBuilder extends Builder
{

    /**
     * Создаёт продукт
     *
     * @return void
     */
    public function buildProduct()
    {
        parent::buildProduct();
        $this->product->setName('The product of the first builder');
    }
}

/**
 * 2-й строитель
 */
class SecondBuilder extends Builder
{

    /**
     * Создаёт продукт
     *
     * @return void
     */
    public function buildProduct()
    {
        parent::buildProduct();
        $this->product->setName('The product of second builder');
    }
}

/*
 * =====================================
 *            USING OF BUILDER
 * =====================================
 */

$firstDirector = new Factory(new FirstBuilder());
$secondDirector = new Factory(new SecondBuilder());

print_r($firstDirector->getProduct()->getName());
// The product of the first builder
print_r($secondDirector->getProduct()->getName());
// The product of second builder

 Выходит, мы разглядели 9 образцов проектирования. Это достаточно длинная статья. Следственно хотелось бы узнать ваше суждение. Есть ли толк в проделанной работе и стоит ли мне закончить цикл, рассказав о структурных образцах и образцах поведения?

 Каждый опубликованный код дозволено обнаружить и на гитхабе. 

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB