Показаны сообщения с ярлыком шаблоны. Показать все сообщения
Показаны сообщения с ярлыком шаблоны. Показать все сообщения

суббота, 11 июня 2016 г.

Шаблоны. Порождающие. Multiton (Мультитон, Пул одиночек)

Цель.

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

UML-диаграмма

Недостатки, Альтернативы, Резюме.

Аналогичны шаблону синглетон. Так же можно считать антипаттерном.

Реализация на PHP.

class Multiton
{
    /** The first instance key. */
    const INSTANCE_1 = '1';

    /** The second instance key. */
    const INSTANCE_2 = '2';
    
    private static $instances = array();
    
    private function __construct() { }

    public static function getFirstInstance()
    {
        return self::getInstance(self::INSTANCE_1);
    }

    public static function getSecondInstance()
    {
        return self::getInstance(self::INSTANCE_2);
    }
    
    public function doSomething() { }
    
    /**
     * @return Multiton
     */
    private static function getInstance($instanceName)
    {
        if (!array_key_exists($instanceName, self::$instances)) {
            self::$instances[$instanceName] = new self();
        }
        return self::$instances[$instanceName];
    }

    private function __clone() { } // Защищаем от создания через клонирование
    private function __wakeup() { } // Защищаем от создания через unserialize
}

// Использование
Multiton::getFirstInstance()->doSomething();
Multiton::getSecondInstance()->doSomething();

Немного доработав этот шаблон можно использовать как базовый класс для всех синглетоновв системе:

abstract class AbstractSingleton
{
    private static $instances = array();
    
    protected function __construct() {/*_*/}
    
    /**
     * @return static
     */
    final public static function me()
    {
        return self::getInstance(get_called_class());
    }
    
    private static function getInstance($class)
    {
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class;
        }
        return self::$instances[$class];
    }

    private function __clone() { } // Защищаем от создания через клонирование
    private function __wakeup() { } // Защищаем от создания через unserialize
}

// Использование
class Foo extends AbstractSingleton
{
    protected function __construct() { /* инициализация для конкретного наследника, если нужна */ }

    public function doSomething() { }
}

class Bar extends AbstractSingleton
{
    public function doSomethingOther() { }
}

Foo::me()->doSomething();
Bar::me()->doSomethingOther();

Нецелевое использование.

Замена ужасного кода немного лучшим на одном из этапов рефакторинга.


Известный факт: статические методы - зло. Причин на то много и сейчас не об этом. Допустим есть проект, где один из классов использует исключительно статические методы. Пусть для простоты - один метод. Проект растёт и класс этот используется в проекте все в большем и большем количестве мест. То есть, чтобы заменить его статический метод на динамический уже требуются значительные затраты. И вот в один прекрасный день выясняется, что в проекте нужен ещё один (и даже не один) класс с похожим функционалом. В случае с динамическими коассами все просто - общий код выносится в абстрактный класс и от него наследуются классы с необходимыми функциями. Но abstract static function - это нонсенс. Выхода два - или делать почти одинаковые статические классы (ужас), или превратить статические классы в динамические, оставив при этом статический вызов и гарантировать при этом, что обращение будет к единственному экземпляру класса (не зря же исходный класс был статическим - что-то этим хотели сказать авторы или чего-то боялись). В решении подобной проблемы может помочь мультитон.

Код до рефакторинга:

class Foo
{
    public static function doSomething() 
    {
        self::doCommonStep();
        self::doSpecificStep();
    }
    
    private static function doCommonStep() { /* Дублированный код */ }
    
    private static function doSpecificStep() { /* Специфичный для Foo код */ }
}

class Bar
{
    public static function doSomething()
    {
        self::doCommonStep();
        self::doSpecificStep();
    }

    private static function doCommonStep() { /* Дублированный код */ }

    private static function doSpecificStep() { /* Специфичный для Bar код */ }
}

// Использование
Foo::doSomething();
Bar::doSomething();

Код после рефакторинга:

abstract class Base
{
    private static $instances = array();

    protected function __construct() {/*_*/}

    /**
     * @return static
     */
    final public static function me()
    {
        return self::getInstance(get_called_class());
    }

    private static function getInstance($class)
    {
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class;
        }
        return self::$instances[$class];
    }
    
    public static function doSomething() 
    {
        static::me()->something();
    }
    
    private function something() 
    {
        $this->doCommonStep();
        $this->doSpecificStep();
    }

    private function doCommonStep() { /* Дублированный код */ }

    abstract protected function doSpecificStep();
}

class Foo extends Base
{
    protected function doSpecificStep() { /* Специфичный для Foo код */ }
}

class Bar extends Base
{
    protected function doSpecificStep() { /* Специфичный для Bar код */ }
}

// Использование
Foo::doSomething();
Bar::doSomething();

пятница, 10 июня 2016 г.

Шаблоны. Порождающие. Singleton (Одиночка)

Назначение.

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

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

UML-диаграмма

Достоинства.

  • единственный экземпляр данного класса в системе

Недостатки.

  • Фактически является глобальной переменной если хранит состояние;
  • Нарушает принцип единственной ответственности (SRP). Помимо выполнения своих непосредственных функций еще и контролирует то, что существует в единственном экземпляре;
  • Снижает тестируемость клиентского кода поскольку вместо него невозможно подставить mock-объект;
  • Повышает связанность кода.

Альтернативы.

Использование шаблонов DependencyInjection или ServiceLocator. Например, фреймворк Symfony позволяет полностью обойтись без синглетона благодаря своей системе DI.

Резюме.

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

Реализация на PHP.

class Singleton
{
    private static $instance = null;  // экземпляр объекта

    private function __construct(){ /* ... @return Singleton */ }  // Защищаем от создания через new Singleton

    /**
     * @return Singleton
     */
    public static function me() // Возвращает единственный экземпляр класса.
    {
        if ( is_null(self::$instance) ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function doSomething() { }

    private function __clone()    { }  // Защищаем от создания через клонирование
    private function __wakeup()   { }  // Защищаем от создания через unserialize
}

// Использование
Singleton::me()->doSomething();

Если в проекте существует несколько синглетонов, то при такой реализации прийдется в каждом из них писать весь вышеприведенный код (кроме метода doSomething(), конечно). Дублирование кода - крайне плохая практика, пусть даже этот код скорее всего меняться никогда не будет. С другой стороны служебные конструкции, реализующие шаблон, в PHP выглядят достаточно громоздко и затрудняют чтение. Так есть ли способ (в PHP) избежать такого дублирования? Начиная с версии 5.4 этот язык получил trait - кусок кода, который можно внедрять в любой объект по своему усмотрению. Насколько хороши треиты в PHP и насколько, где и как стоит их использовать - отдельная тема. Здесь же посмотрим можно ли использовать trait для при реализации синглетонов и насколько такое использование правильно.

В ряде интернет-источников (в том числе и в википедии) приводится пример подобного кода:

trait Singleton 
{
    static private $instance = null;

    private function __construct() { /* ... @return Singleton */ }  // Защищаем от создания через new Singleton

    public static function me() 
    {
        if ( is_null(self::$instance) ) {
            self::$instance = new static();
        }
        return self::$instance;
    }
    
    private function __clone() { /* ... @return Singleton */ }  // Защищаем от создания через клонирование
    private function __wakeup() { /* ... @return Singleton */ }  // Защищаем от создания через unserialize
}

/**
  * Class Foo
  * @method static Foo me()
  */
class Foo 
{
    use Singleton;

    public function doSomething() { }
}

// Применение
Foo::me()->doSomething();

А теперь попробуем добавить в Foo публичный конструктор:

class BrokenSingleton
{
    use Singleton;

    public function __construct() { }

    public function doSomething() { }
}

(new BrokenSingleton())->doSomething();

Теперь это уже не синглетон. Замечу, что реализация без trait сделать подобное не позволяет. Это достаточно большой минус, чтобы не использовать подобную конструкцию вообще. Есть другой, более надежный и правильный способ избежать дублирования кода - шаблон мультитон. Хотя он и предназначен для другого, PHP благодаря своей нестрогой типизации позволяет использовать его в том числе и для сокращения дублирования кода.

Реализация на  Scala.

В Scala есть ключевое слово object, обозначающее, что этот класс будет создан в единственном экземпляре. Поэтому реализация singletone выглядит совсем просто:

object Singleton {}

// Или как объект-компаньон (в одном файле):
class Foo {}

object Foo {}