0,0
рейтинг
10 марта 2014 в 11:48

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

ООП*, PHP*
Тема заезженная до дыр, не спорю… Вероятно, для опытных разработчиков моя статья будет мало, чем полезна. Я бы рекомендовал её к прочтению тем, кто только начал осознавать, что его коду чего-то не хватает, и что он созрел для вникания в это далёкое понятие – «паттерны». По себе помню, что довольно долгое время я путался в шаблонах, иногда даже не понимая, чем один отличается от другого. Именно этот факт стал основой для моей статьи. Примеры в ней не будут реальными. Они будут абстрактными и максимально простыми. Однако я постараюсь все примеры держать в едином контексте, чтобы можно было наглядно видеть отличия их использования в одной и той же ситуации. Я не буду нагружать классы лишним функционалом, чтобы можно было понять, какая именно часть кода имеет непосредственное отношение к шаблону. Главными героями примеров станут 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 (array_key_exists($key, self::$data)) {
            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 (array_key_exists($id, self::$products)) {
            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 (!(self::$instance instanceof self)) {
            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 (!(self::$instances[$className] instanceof $className)) {
            self::$instances[$className] = new $className();
        }
        return self::$instances[$className];
    }

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

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

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

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

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

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

/**
 * Интерфейс пула одиночек
 */
abstract class Factory extends FactoryAbstract
{

    /**
     * Возвращает экземпляр класса, из которого вызван
     *
     * @return static
     */
    final public static function getInstance()
    {
        return parent::getInstance();
    }

    /**
     * Удаляет экземпляр класса, из которого вызван
     *
     * @return void
     */
    final public static function removeInstance()
    {
        parent::removeInstance();
    }
}

/*
 * =====================================
 *           USING OF MULTITON
 * =====================================
 */

/**
 * Первый одиночка
 */
class FirstProduct extends Factory
{
    public $a = [];
}

/**
 * Второй одиночка
 */
class SecondProduct extends FirstProduct
{
}

// Заполняем свойства одиночек
FirstProduct::getInstance()->a[] = 1;
SecondProduct::getInstance()->a[] = 2;
FirstProduct::getInstance()->a[] = 3;
SecondProduct::getInstance()->a[] = 4;

print_r(FirstProduct::getInstance()->a);
// array(1, 3)
print_r(SecondProduct::getInstance()->a);
// array(2, 4)

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

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

Пример 4.2
<?php
/**
 * Интерфейс сложного пула одиночек
 */
abstract class RegistryFactory extends FactoryAbstract
{

    /**
     * Возвращает экземпляр класса, из которого вызван
     *
     * @param integer|string $id - уникальный идентификатор одиночки
     * @return static
     */
    final public static function getInstance($id)
    {
        $className = static::getClassName();
        if (isset(self::$instances[$className])) {
            if (!(self::$instances[$className][$id] instanceof $className)) {
                self::$instances[$className][$id] = new $className($id);
            }
        } else {
            self::$instances[$className] = [
                $id => new $className($id),
            ];
        }
        return self::$instances[$className][$id];
    }

    /**
     * Удаляет экземпляр класса, из которого вызван
     *
     * @param integer|string $id - уникальный идентификатор одиночки. Если не указан, все экземпляры класса будут удалены
     * @return void
     */
    final public static function removeInstance($id = null)
    {
        $className = static::getClassName();
        if (isset(self::$instances[$className])) {
            if (is_null($id)) {
                unset(self::$instances[$className]);
            } else {
                if (isset(self::$instances[$className][$id])) {
                    unset(self::$instances[$className][$id]);
                }
                if (empty(self::$instances[$className])) {
                    unset(self::$instances[$className]);
                }
            }
        }
    }

    protected function __construct($id)
    {
    }
}

/*
 * =====================================
 *           USING OF MULTITON
 * =====================================
 */

/**
 * Первый пул одиночек
 */
class FirstFactory extends RegistryFactory
{
    public $a = [];
}

/**
 * Второй пул одиночек
 */
class SecondFactory extends FirstFactory
{
}

// Заполняем свойства одиночек
FirstFactory::getInstance('FirstProduct')->a[] = 1;
FirstFactory::getInstance('SecondProduct')->a[] = 2;
SecondFactory::getInstance('FirstProduct')->a[] = 3;
SecondFactory::getInstance('SecondProduct')->a[] = 4;
FirstFactory::getInstance('FirstProduct')->a[] = 5;
FirstFactory::getInstance('SecondProduct')->a[] = 6;
SecondFactory::getInstance('FirstProduct')->a[] = 7;
SecondFactory::getInstance('SecondProduct')->a[] = 8;

print_r(FirstFactory::getInstance('FirstProduct')->a);
// array(1, 5)
print_r(FirstFactory::getInstance('SecondProduct')->a);
// array(2, 6)
print_r(SecondFactory::getInstance('FirstProduct')->a);
// array(3, 7)
print_r(SecondFactory::getInstance('SecondProduct')->a);
// array(4, 8)

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

А теперь, пока ещё не слишком поздно, верну мечтателей с небес на землю. Шаблон Одиночка и его продвинутые братья, несомненно, могут быть полезны, но не надо забываться и лепить его где нужно и где не нужно. Напомню (или поведаю), что есть такой антипаттерн, «Одиночество» (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 SecondProduct();
    }
}

/**
 * Первый продукт
 */
class FirstProduct implements Product
{

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

/**
 * Второй продукт
 */
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();
    }
}

/**
 * Продукт второй фабрики
 */
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;
    }
}

/**
 * Первый продукт
 */
class FirstProduct implements Product
{

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

/**
 * Второй продукт
 */
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();
    }
}

/**
 * Первый строитель
 */
class FirstBuilder extends Builder
{

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

/**
 * Второй строитель
 */
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 шаблонов проектирования. Это довольно длинная статья. Поэтому хотелось бы узнать ваше мнение. Есть ли смысл в проделанной работе и стоит ли мне завершить цикл, рассказав о структурных шаблонах и шаблонах поведения?

Весь опубликованный код можно найти и на гитхабе.
Алексей Кузнецов @Webtoucher
карма
9,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (83)

  • +15
    По-моему, в последнее время создание статей на Хабре превратилось в создание ради создания =)
    • +1
      Почему же? При искреннем изложении материала, автор топика может привнести нечто своё, новое, под новым углом зрения; чем больше граней, под которыми смотрите на материал, тем больше информации для размышления у Вас будет. Тогда как строгий, целостный книжный стиль изложения, к которому не то, что не придерёшься, а всё так подогнано, что не за что зацепиться мыслью, может показаться сухим, и не очень успешно усваиваться. Как-то так.
  • +9
    Я бы рекомендовал её к прочтению тем, кто только начал осознавать, что его коду чего-то не хватает, и что он созрел для вникания в это далёкое понятие – «паттерны».

    Вот это как раз самый опасный вариант. После него в коде везде появляются «паттерны», причем как там, где надо, так и там, где не надо.

    (особенно когда статья сама (а) путает разные паттерны, и (б) не объясняет, зачем они, собственно, нужны)
    • 0
      Ну я сразу сказал, что конкретики не будет. Википедию никто не отменял. Там можно почитать, зачем паттерны нужны. Цели статьи были другими.
      А вот за более раскрытые замечания был бы признателен. Где именно я перепутал разные паттерны?
      • +5
        Пул объектов это пул объектов и называть класс Fabric — вводит в заблуждение (фабрика порождает, но не хранит). Во-вторых, каноническая реализация пула объектов несколько иная. Отличия: мало того, что он хранит объекты, он же их и создает (благо, в PHP это вообще не трудно реализовать, но поскольку в php не нужно следить за ссылками на объект, в этом смысла особого нет. Он так то и придуман для того, чтобы не терять объекты, где нет сборщика мусора, и чтобы их не удалять и не создавать по многу раз, а постоянно хранить до завершения работы приложения). Вдобавок, пул сам по себе не должен быть статическим, а должен быть классом, от которого можно создавать объекты. А вот эти объекты уже можно обертывать в статические классы или синглтоны. Это по той причине, что для повышения производительности выборки и добавления объектов в пул, их может быть несколько (разбиты на логические группы, например, «товары», «пользователи», «комментарии»).

        Кривая реализация одиночки.
        Че вдруг деструктор закрыт? Да, скорее всего одиночка проживет все время существования приложения, но удалить её может понадобиться. Грамотнее было бы сделать примерно так: добавить переменную, которая обозначала бы, что объект больше не нужен, в деструкторе проверять её, и удалять соответственно.
        Почему одиночку нельзя вдруг создавать через конструктор? Почему не перенести логику из function getInstance() в конструктор?

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

        Главное замечание ко всей статье — не понятно, зачем она нужна?
        На чем, но на php реализация большинства паттернов элементарна и интуитивна. И давать лишь код этого, не описывая сами паттерны и случаи их применения — глупо. Уж лучше допишите их в ту же википедию в примеры, если хотите сделать что-то полезное, а не написать «статью ради статьи».
        • 0
          Про абстрактную фабрику я немного не прав, но ваш пример все равно не очень понятен, потому что сразу же возникает вопрос — почему мы напрямую не могли взять этот объект? Пример с той же википедии несколько нагляднее.

          И да, глупо писать тут название паттерна, приводит пример реализации, но отправлять человека читать о самом паттерне на вики, где уже есть пример на этом же языке.
        • +2
          Почему одиночку нельзя вдруг создавать через конструктор? Почему не перенести логику из function getInstance() в конструктор?

          Простите, это как? Конструктор в PHP (как и почти во всех ОО языках) не обладает блоком возврата.
          • –2
            Да неужели :)?

            <?php
            var_dump(new StdClass()); //returns: object(stdClass)#1 (0) { }
            
            • +1
              Сарказм неуместен. Вы же понимаете разницу между языковой конструкцией new и конструктором класса?
              • –2
                Тут нет сарказма. Насколько я в курсе в PHP конструктор класса вызывается с помощью оператора new и никак иначе его вызвать нельзя. И оператор new для других целей не применяется.

                Но вообще, я не силен в ООП, могу и ошибаться.

                Я ниже привел фрагмент кода. Давайте его обсудим, помогите разобраться.
                • +1
                  Языковая конструкция new создает экземпляр объекта указанного класса. Вызов конструктора при этом является побочным действием и не влияют (кроме брошенного исключения) на поведение new, и как следствие интерпретатор PHP не учитывает блок возврата (return $something) в конструкторе.
                  Но вообще, я не силен в ООП, могу и ошибаться.

                  С этого и стоило бы начать. Для вопросов есть соответствующий сервис.
          • 0
            Да, что-то меня занесло, просто последние пол года пишу в основном на JS и немного на С (чистом), в первом так можно без проблем, во втором так нельзя в принципе.

            Но если не брать случай, что именно объект должен быть уникальным и в единственном экземпляре, а не данные, с какими он работает, то это не сложно (он просто будет агрегировать статичные данные или ту же одиночку). А если брать, что именно объект должен быть уникальным — придется написать нехилый костыль.
        • 0
          Почему одиночку нельзя вдруг создавать через конструктор? Почему не перенести логику из function getInstance() в конструктор?

          Потому что self() вызывает конструктор (а в этот момент он ещё не instanceof self — значит он ещё раз вызовет self(), который, в свою очередь, ещё раз вызовет self(), который...). Если содержимое getInstance() перенести в __construct(), то выполнение просто зациклится. Всё равно, что писать while(true){/*здесь нет никаких break, exit и т.п.*/}.
          • +1
            А переписывать код так, чтобы не было зацикливания, — тоже нет смысла; в __construct() нет смысла использовать return, потому что __construct() в любом случае возвращает новый экземпляр класса.
            • 0
              Ну, наверное, правильней сказать, что конструктор вообще ничего не возвращает… То, что мы помещаем в конструктор, вызывается после создания экземпляра класса.
        • 0
          Почему одиночку нельзя вдруг создавать через конструктор? Почему не перенести логику из function getInstance() в конструктор?

          Потому что при каждом вызове new всегда создаётся новый инстанс и ссылка именно на него возвращается как результат. Конструктор не может ни предотвратить создание, ни повлиять на возвращаемую ссылку — только инициализировать состояние создаваемого объекта, ну или вызвать ошибку/исключение, чтобы ничего не вернулось вообще. В общем два вызова new всегда вернут разные объекты и изменить это в пхп невозможно.
      • +2
        Цели статьи были другими.

        Какими?

        Википедию никто не отменял. Там можно почитать, зачем паттерны нужны.

        Так и GoF никто не отменял, там можно почитать и все остальное про паттерны.

        Где именно я перепутал разные паттерны?

        Реестр — не порождающий паттерн, и он здесь низачем не нужен.
        Пул объектов — это не порождающий паттерн, он не является частным случаем реестра, и в него никогда не «складывают» объекты (они появляются там «магией»).
        Что такое «пул одиночек» и зачем он нужен — непонятно.
        Фабричный метод реализуется в том же классе, который им порождается (и, кстати, поэтому ваша реализация синглтона — это частный случай фабричного метода); а то, что у вас описано, как фабричный метод — это фабрика.
        Чем отличается Lazy Initialization от Singleton в вашем описании?
        Ваша реализация Builder ничем не отличается от фабрики, хотя на самом деле у них есть существенные отличия.
  • +2
    В примере 5, SecondFactory создает FirstProduct
    • 0
      Спасибо, не углядел. Исправил.
  • +2
    Нет самого популярного шаблона в списке — DI. Он частично порождающий.
    • 0
      Покажите DI на простом реалистичном примере, пожалуйста.
      • +1
        pimple.sensiolabs.org/
        Простейшая реализация из сотни строк.
        • +1
          Там реализация… а мне не понятно, зачем это вообще.
          • 0
            А вам понятно, зачем нужен Inversion of Control?
            • 0
              Не очень.

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

              На этом мои познания пока исчерпываются.
              • 0
                И что нужно это для того, чтобы методы, принимающие объекты в качестве параметров могли принимать объекты не одного конкретного класса, а всех классов какой-то ветви иерархии. А это, в свою очередь, нужно нужно чтобы упростить методы из предыдущего предложения и избежать их дублирования.

                Нет. В том смысле, что вообще нет.

                IoC нужен для того, чтобы мы могли в любой момент заменить одну реализацию другой; а это, в свою очередь, нужно для тестирования и расширяемости приложения. Подробности — у Роберта Мартина.
                • 0
                  А… в таком случае, в моем примере это есть.

                  $STATE["db"] = get_db_object();

                  Я понимаю, что для вас такой синтаксис не привычен. Здесь в свойство db объекта «Приложение» записывается объект «База данных», реализация которого может быть любой.

                  Ушел читать Мартина.
                  • 0
                    Есть-то есть (в какой-то части), только реализовано криво (вместо того, чтобы использовать Service Locator или Dependency Injection, вы изобретаете свой полувелосипед, похожий на SL, но существенно кривее).
                    • 0
                      Вы же понимаете, что когда вы меня ругаете: «криво», «ошибка», было бы полезно пояснить в чем именно кривизна…. Я-то этого не вижу, иначе бы так не сделал.

                      Либо же нет никакой кривизны, просто вы не понимаете мой код.

                      • 0
                        «Кривизна» в том, что (а) вы используете некий глобальный объект для хранения всей (т.е., несвязанной) информации; это т.н. god object, функции которого нельзя определить (б) вы используете строковые ключи вместо «нормальных» свойств или, еще лучше, типа сервиса в качестве ключа, (ц) вместо того, чтобы создавать зависимость в момент ее необходимости (причем — с нужным жизненным циклом), вы ее явно прописываете в этот глобальный объект и, наконец, (д) service locator вообще последнее время считается антипаттерном.
                        • 0
                          (а) В фреймворках часто можно встретить объект $app. Оттого, что я назвал его $STATE не делает его «кривым». В этой переменной хранится состояние приложения. Информация связана — вся она относится к приложению.
                          (б) Чем, по-вашему, "$obj->property" остроконечников лучше чем "$obj[«property»] тупоконечников?
                          Мне просто нравится такой синтаксис. Но можно заменить тип $STATE с array на StdClass и обращаться к его свойствам, используя объектную нотацию.

                          (ц), (д) — тут прошу поподробнее. Чувствую ценность в вашем комментарии, но нужно больше деталей.
                          • 0
                            В фреймворках часто можно встретить объект $app

                            В тех фреймворках, с которыми я имел дело, функции этого объекта строго ограничены, а все прочее делегировано другим.

                            Информация связана — вся она относится к приложению.

                            Вся информация в приложении к нему относится. Это не делает ее связанной.

                            Чем, по-вашему, "$obj->property" остроконечников лучше чем "$obj[«property»] тупоконечников?

                            Семантикой. Первая запись подразумевает фиксированный набор хорошо известных свойств, вторая — изменяемый словарь.

                            (ц), (д) — тут прошу поподробнее. Чувствую ценность в вашем комментарии, но нужно больше деталей.

                            Тут придется пересказать всю историю реализаций IoC. Давайте я вас снова отправлю к хорошей книжке:
                            Dependency Injection in .NET. Это как раз тот случай, когда все описано очень конкретно и по делу.
                            • 0
                              Первая запись подразумевает фиксированный набор хорошо известных свойств, вторая — изменяемый словарь.


                              В .Net возможно, но не в PHP.

                              <?php
                              class TheClass
                              {
                                  public $ownProperty;
                                  public function getProp(){
                                      echo "Не существующее свойство: " . $this->property . "<br>";
                                  }
                              }
                              
                              $obj = new TheClass();
                              $obj->property = "Существует.";
                              $obj->getProp(); // Не существующее свойство: Существует.
                              
                              • 0
                                Я говорю про семантику записи, а не про конкретные возможности языка. Вы Макконнела читали?
      • +1
        Советую ознакомиться с презентацией Fabien Potencier (создателя вышеупомянутого Pimple, мэйнтейнера Symfony 2 и CEO Sensio Labs). Мне, в свое время, она очень помогла.
        • 0
          Надеюсь и мне поможет. Уже посмотрел 38 слайдов. Для демонстрации Dependancy Injection выбран хороший пример — класс «Пользователь» и хранение состояния в сессии.
    • 0
      Вы бы приличия ради уточнили, что именно вы понимаете под DI, а то есть ведь варианты.
  • 0
    Наверное, один из самых популярных шаблонов. Как правило, его все запоминают первым. А ещё при поиске работы про него очень любят спрашивать на собеседованиях. Еще надо знать, что многие считают этот шаблон антипаттерном. Я с этим мнением соглашусь в некоторой степени — части пытаются сделать синглтоном те объекты, которые никогда должны ими быть. К примеру, объект доступа к базе.
    И это рано или поздно выливается в проблемы.
  • +1
    Основное применение реестра – в качестве безопасной замены глобальным переменным.

    А можно подробнее про этот момент? Чем он безопаснее?

    Глобальные переменные доступны для чтения/записи из любого места кода. Реестр — тоже. С помощью реестра можно просто организовать всё более централизованно. Но при чём тут «безопаснее»?
    • –1
      Ты наверное не помнишь, почему в своё время все отказались от использования глобальных переменных?? Предлагаю воспользоваться любимым поисковиком… ;)
      • 0
        Я знаю две проблемы с глобальными переменными:
        1) конфликт имен
        2) условно неопределенное поведение при импортировании ключей суперглобальных массивов в глобальную таблицу имен.

        Первую проблему решают префиксы и пространства имен.
        Вторая проблема решена отказом от директивы register_globals в PHP 5.4.

        Отказываться от глобальных переменных я бы пока не стал.
      • 0
        Суть Registry в централизации доступа.

        Основное применение реестра – в качестве безопасной замены глобальным переменным

        Где Вы вообще такой бред находите?
      • 0
        И за такой ответ спасибо. Хотя, IMHO, посылать на х… яндекс или в ж… википедию на каждый вопрос в комментариях — много труда не нужно.
        • 0
          Как и погуглить.
          • 0
            Обратите внимание, что я с самого начала спрашивал про то, «почему Registry безопаснее глобальных переменных», а не про «почему глобальные переменные плохо».

            Именно интересовал вопрос про безопасность шаблона Registry, а не про то, почему лучше не использовать глобальные переменные.
          • 0
            Посылать к поисковикам плохо еще и потому (и это мое личное мнение), что в нашу дискуссию, обмен мнениями, привлекается мнение каких-то посторонних личностей. И ладно, если это признанные авторитеты, но зачастую это не так.
    • 0
      Как минимум, в Registry::set() можно вставить debug_print_backtrace() и понять, откуда пришло это значение.

      В случае глобальных переменных отследить подобные вещи будет проблематично:

      $_GET[rand(1,100)]  = rand(1,100);
      

      • 0
        Вы, похоже, говорите тут о суперглобальных переменных.
        Вот отличии от суперглобальной $_GET, имеющей тип массива, обычная глобальная переменная может быть экземпляром какого-то (любого) класса со своим сеттером.
      • 0
        Кстати, интересная идея. Можно в геттере с помощью $stack = debug_backtrace() получить стек вызовов и взять значение $stack[1] (потому что в $stack[0] будет Registry), а потом через class_parents() получить всех предков. И, например, если в предках есть Controller, запретить напрямую использовать экземпляр класса DB (ссылку на который удобно хранить в реестре), чтобы авторы дополнительных модулей в контроллерах не писали SQL-запросы.

        Это, конечно, на правах шутки, но в каждой шутке есть доля шутки.

        Реестр действительно может дать больше контроля, оставляя при этом плюсы «глобальности».
    • 0
      В общем случае ничем, но есть возможность переопределения логики присваивания и возврата значений, чего нет у переменных. Например, в реестре мы легко можем:
      — сделать присваивание одноразовым
      — реализовать сложные проверки присваиваний (валидация типов и значений, например, в том числе для отдельных ключей)
      — реализовать возвращение значений по умолчанию для неинициализированных элементов, в том числе для отдельных ключей, в том числе с конструированием объектов (непосредственно или через фабрики).
      — сделать несколько инстансов реестра (если реализовать его как обычный объект, а не статический класс или синглтон) с разными значениями для одних ключей.
  • 0
    Признаю, в данном примере мало смысла. Здесь использование этого шаблона не оправдано. Я просто хотел показать его смысл.


    А-аа--аа-аа! (крик души).

    Я, кстати, целевая аудитория статьи. Кучу всего уже читал про паттерны, но до сих пор с трудом понимаю приводимые в статьях примеры и как они связаны с, например, вебсайтами (много процентов из которых на PHP).

    Взять хотя реестр и пул объектов, вроде бы понятные сущности.
    Я использую их для хранения состояния приложения.

    Чтобы отстраниться от стереотипов, назовем его $STATE.

    Подскажите, Благородные Доны, правильно ли я вижу паттерны в нижеследующем примитивном, но более приближенном к земле, как мне кажется, примере:

    1) $STATE = array() — «реестр», он же «пул объектов» (db, page), он же «синглтон» (ибо глобальная переменная).
    2) get_page_by_uri_from_somewhat_storage() — «фабрика» (или абстрактная?). Или это вообще «Строитель»?

    <?php
    require_once "classes/DB_Fabric.php";
    require_once "classes/Renderer_Fabric.php";
    
    $STATE = array();
    
    $STATE["config"] = include "path/to/config.php";
    $STATE["uri"]    = $_SERVER["REQUEST_URI"];
    $STATE["db"]     = get_db_object();
    $STATE["page"]   = get_page_by_uri_from_somewhat_storage();
    $STATE["output"] = render_page();
    
    echo $STATE["output"];
    exit();
    
    function get_db_object(){  // Возвращает объект "База данных" для текущего uri согласно конфигурации.
        global $STATE;
        
        return new DB_Fabric($STATE["config"]["db"], $STATE["uri"] );
    }
    function get_page_by_uri_from_somewhat_storage(){  // возвращает объект "Текущая страница" 
        global $STATE;
    
        if ( is_page_on_filesystem() ){
            return load_page_from_filesystem();
        } elseif( is_page_in_db() ) {
            return get_page_from_db();
        } else {
            return get_404_page();
        };
    };
    function is_page_on_filesystem(){
        global $STATE;
        
        $page_filename = $S["state"]["config"]["path_to_pages"] . $STATE["uri"];
        
        return is_readable($page_filename);
    };
    function load_page_from_filesystem(){
        global $STATE;
        
        return file_get_contents( $S["state"]["config"]["path_to_pages"] . $STATE["uri"] );
    };
    function is_page_in_db(){
        global $STATE;
        
        if ( $STATE["db"]->find("page", "by_uri", $STATE["uri"]) ) return true;
        else return false; 
    };
    function get_page_from_db(){
        global $STATE;
        
        return $STATE["db"]->find("page", "by_uri", $STATE["uri"]);
    }
    function render_page(){  //Возвращает представление страницы для User-Agent, используя подходящий renderer для текущего uri согласно конфигурации
        global $STATE;
        
        $renderer = new Renderer_Fabric( $STATE["config"], $STATE["uri"] );
        return $render->render( $STATE["page"]->data );
    }
    


    Покритикуйте мой пример.

    Как его можно доработать, чтобы продемонстрировать другие порождающие шаблоны? Или другие тут не нужны?

    • 0
      Как его можно доработать, чтобы продемонстрировать другие порождающие шаблоны?

      Это неправильный подход. Правильный подход звучит так: у меня есть следующая задача [...], какой паттерн подходит для ее решения?
      • 0
        Мы изучаем паттерны, поэтому задача обратная — понять, какие прикладные задачи с помощью какого паттерна решаются и как.
        • +3
          понять, какие прикладные задачи с помощью какого паттерна решаются и как.

          Паттерны не решают прикладные задачи, паттерны структурируют код, который решает прикладные задачи.
      • 0
        Вообще есть такие задачи:
        — ввод данных от пользователя
        — вычисления (реализация бизнес-логики)
        — хранения входных данных
        — хранение промежуточных и окончательных результатов вычислений
        — генерация HTML- (XML-, JSON-, ...) представления окончательных результатов вычислений
        — вывод данных пользователю.

        Какие паттерны подходит для их решения? Если несколько, то каким отдавать предпочтение и в каких случаях?

        Ссылки можно не давать, интересует ваше (или тех, кто найдет смелость ответить на коммент) мнение и опыт.
        • 0
          На SO такие вопросы закрываются как too broad. Что значит «ввод данных от пользователя»? Консоль? Веб-приложение? GUI?

          Для каждого из ваших пунктов есть список из паттернов, которые применяются в разработке. Вы GoF читали? А Фаулера? Там, в общем-то, все расписано, какой смысл цитировать тут книгу целиком?
          • 0
            Я предполагаю, что мы в контексте топика, т.е. задачи не слишком широкие, речь все еще о PHP. Давайте сузим до веб-приложений.

            Этих великих деятелей, упомянутых вами, не читал. Не мой уровень, не дорос.
            Но я пробовал читать Мэта Зандстру «PHP. Объекты, шаблоны и методики программирования» (не смог оценить шедевра) и сейчас начал читать Sanders W. — Learning PHP Design Patterns. Успел узнать о порождающих шаблонах фабрика и прототип. Но примеры там тоже не очень ясные.
            • 0
              Ну так вы прочитайте. Честное слово, там описано существенно лучше, нежели я своими словами перескажу.

              Design Patterns: Elements of Reusable Object-Oriented Software
              Patterns of Enterprise Application Architecture
              • 0
                Это далеко от моих реальных задач. Когда я читаю подобное, мне сложно представить, где я могу это применить в своих проектам. От того и не воспринимается информация…

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

                Потому и пытаю всех вопросами.
                • +1
                  В моей практике есть ровно два способа обучения людей, которые не могут прочитать книжку и увидеть применимости приведенного кода. Первый — это работа в паре с более опытным программистом, который будет разбирать конкретный код в ходе его написания и показывать, что именно должно быть применено (и в обратную сторону, анализ кода этого программиста с конкретными примерами). И второй — это прочитать все эти книжки лет через пять-десять успешной работы программистом, хлопнуть себя по лбу, и сказать «ах, так вот как это называется, оказывается».
                  • +1
                    На самом деле человек прав. Я знаю огромное количество программистов, которые изучают шаблоны проектирования, но совершенно не понимают когда и какой использовать. Да и тема эта в литературе не раскрыта.
                    Даже ссылки, которые вы даете человеку не дадут никакого понимания, что и где использовать. То что в каждом шаблоне описано где он может быть использован — абсолютно ничего не дает, так как слишком большое количество пересекающихся шаблонов по решению задач.

                    И вообще, я не считаю, что можно определить когда и где какой шаблон использовать, они нужны для другого. Они нужны для коммуникации между разработчиками, что бы один мог передать другому быстро, каким способом он реализовал тот или иной кусок логики.
                    • 0
                      но совершенно не понимают когда и какой использовать.


                      Что комментарии, в общем-то, и подтверждают.
                  • 0
                    Я пока иду по второму пути.
    • 0
      Конкретика.

      $STATE = array() — «реестр», он же «пул объектов» (db, page), он же «синглтон» (ибо глобальная переменная).

      Ни одно из них. Это просто глобальный словарь (весьма пакостный антипаттерн со всех сторон).

      get_page_by_uri_from_somewhat_storage() — «фабрика» (или абстрактная?). Или это вообще «Строитель»?

      Ни то, ни другое, ни третье. Метод, читающий что-то откуда-то, в норме не относят к порождающим паттернам.
      • 0
        Хорошо, «глобальный словарь» и «реестр» это разные слова. Но означают они что-то близкое, если не одно и то же. И туда и сюда можно положить данные и извлечь, и можно сделать это из любого места программы.

        Метод, читающий что-то откуда-то, в норме не относят к порождающим паттернам


        Что тут имеется в виду? Данные в программу вводятся извне, в любом случае, что-то откуда-то прочитается: от пользователя, из конфига, из хранилища. Да и и get_page_by_uri_from_somewhat_storage() сам не читает ничего, а вызывает другие функции.
        • 0
          Но означают они что-то близкое, если не одно и то же.

          Не одно и то же. Вот такие мелочи и отличают паттерн (то есть, отработанное временем решение) от вроде-бы-того-же-самого, но на самом деле — опасной ошибки.

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

          Что тут имеется в виду?

          Имеется в виду, что тот метод, о котором вы говорите — это не фабрика (ни в каком ее виде).
          • 0
            от такие мелочи и отличают паттерн

            Хорошо бы вы указали на эти мелочи, пока они остаются загадкой.

            совершенно не понятно, зачем примененное, и от этого очень опасное

            Этим грешат и паттерны, когда они не понятны.

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

            Применяется паттерн только в главное программе или в контроллере (при использовании MVC).

            Кроме того, такой подход позволяет описывать главную программу (контроллер) декларативно. Из примера этого не видно, впрочем

            • 0
              Хорошо бы вы указали на эти мелочи, пока они остаются загадкой.

              Реестр содержит семантически связанные и (в норме) однотипные объекты. Может быть реестр сервисов, может быть реестр (конкретных) данных, но не может быть реестра всего — это уже god object.

              Использование $STATE направлено на снижение связности модулей программы

              Угу, вместо того, чтобы связать два модуля через хорошо известный обоим контракт, нужно оба их связать с вашим $STATE, причем без возможности определить контракт. Как вы (инструментально) отслеживаете корректность обращения к данным в $STATE?

              Глобальная область видимости — это плохо. Это плохо именно потому, что у вас нарушается принцип единой ответственности.
              • 0
                Реестр содержит семантически связанные и (в норме) однотипные объекты.

                Справедливо.

                связать два модуля через хорошо известный обоим контракт,

                В общем модулей сильно больше двух. И каждый из них связан только со $STATE. $STATE не обязан быть массивом. В более сложных случаях это будет объект с кастомными сеттерами/геттерами.

                Глобальная область видимости — это плохо. Это плохо именно потому, что у вас нарушается принцип единой ответственности.

                Нарушать этот принцип вовсе не обязательно. Глобальная область это просто верхний уровень. Погружение внутрь хорошо для библиотек и т.п., но для основной программы это, по моему, лишнее.
                • 0
                  В общем модулей сильно больше двух. И каждый из них связан только со $STATE.

                  Представьте себе, что у вас в системе полторы тысячи модулей, и в каждом из них где-нибудь по три-пять сегментов данных, которыми нужно обмениваться. Пять-восемь тысяч свойств $STATE, you kidding?

                  Ну и вы не ответили про средства инструментального контроля.

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

                  Отнюдь. Это все хорошо только пока у вас мало кода, и он простой. С возрастанием сложности необходимость инкапсуляции растет.

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

                Это откуда? Всегда воспринимал реестр как некое рантайм хранилище чего угодно. Да, можно делать специализированные, типизированные, но это уже ближе к пулу, нет?
                • 0
                  Это из чтения Фаулера и моего понимания SRP. Иначе неизбежен год-обжект.
                  • +1
                    У реестра одна ответственность — хранить значения (в том числе объекты, ресурсы и массивы в контексте PHP) в рантайме для всего приложения. Какое отношение имеет тип этих значений или их семантическая близость к ответственности?
                    • +1
                      У реестра одна ответственность — хранить значения (в том числе объекты, ресурсы и массивы в контексте PHP) в рантайме для всего приложения.

                      Слово «всего» — лишнее. Не для всего приложения, а для заинтересованных сторон. Соответственно, дальше у вас начинается типичный граф зависимостей. Чем больше зависимостей (для конкретного реестра), тем сложнее им управлять и поддерживать. Соответственно, если вы разделяете реестр сервисов с реестрами данных, а в реестрах данных разделяете реестр пользователей и реестр документов, то каждый из реестров становится существенно проще (не говоря уже о том, что пропадает необходимость придумывать способы обеспечения уникальности ключей).
                      • 0
                        Ну, если есть нужда хранения разнотипных наборов однотипных данных, то естественно лучше каждый набор однотипных данных выделять в отдельную сущность. Но вот если однотипных значений одно-два (типа коннектов к СУБД), то вполне вероятно, что создание отдельного реестра на каждый тип будет выглядеть оверинженерингом. Хотя, конечно, общего рецепта нет.
                        • 0
                          Но вот если однотипных значений одно-два (типа коннектов к СУБД), то вполне вероятно, что создание отдельного реестра на каждый тип будет выглядеть оверинженерингом.

                          Плохой пример. Зачем хранить коннекты к БД в общедоступном реестре?

                          (впрочем, реестр — вообще очень посредственный паттерн, сам Фаулер об этом пишет)
  • 0
    Я просто оставлю это здесь:

    «Приемы объектно-ориентированного проектирования. Паттерны проектирования»
    Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес

    ISBN 978-5-469-01136-1, 5-272-00355-1, 0-201-63361-2,5-469-01136-4; 2007 г.
    • 0
      Просто попробуй разобраться в этой книге и применить эти паттерны к языку отличному от smalltalk
      • 0
        Разобрался.
        Вообще говоря это книжка скорее теоретическая, нежели практическая и не важно на каком из языков примеры.

        Если человек не способен понять содержание этой книги, наверное ему пока что рано в паттерны.
        • 0
          По PHP есть похожая книжка, которая также может навести порядок в мыслях:
          «PHP. Объекты, шаблоны и методики программирования»
          Мэт Зандстра
          ISBN 978-5-8459-1689-1, 978-1-43-022925-4; 2011 г.

          Лично мне очень понравилась, рекомендую. Из особенностей: похоже, она для версии PHP 5.3.

          Упомянута, в частности, тут.
  • 0
    Итак, мы рассмотрели 9 шаблонов проектирования. Это довольно длинная статья. Поэтому хотелось бы узнать ваше мнение. Есть ли смысл в проделанной работе и стоит ли мне завершить цикл, рассказав о структурных шаблонах и шаблонах поведения?

    Спасибо за пост! Смысл определённо есть. Ждём продолжения.
  • –2
    Очень наглядный пост, пожалуйста, продолжайте.
  • 0
    deleted

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.