PHP

Паттерны проектирования

Если вы взялись всерьёз программировать на ООП, готовьтесь к тому, что тут на каждом шагу грабли и вилы. Код более сложный и менее управляемый в силу своей запутанности, это приводит к куче логических ошибок, которые диагностировать очень трудно. Для того, чтобы избежать подобного рода недоразумений, а так же для того, чтобы как то упорядочить этот запутанный код, придуманы так называемые паттерны. Тоесть по русски - костыли, которые помогают не рухнуть сооружению, если оно строится несколькими подрядчиками или неумелыми гастарбайтерами.

Синглетон (Singleton)

Самым ярким и академическим примером является работа с базой данных.
Вот допустим у нас имеется класс для работы с БД.
<?php

class dataBase 
{

    public function __construct() 
    {
        echo 'Тут как бы коннект к базе<br>';
/*        mysql_connect( DBSERVER, DBUSER, DBPASSWORD );  
        mysql_select_db( DATABASE ); */        
    }
    
    public function mysqlAssoc($query) 
    {
        echo 'А тут как бы запрос: '. $query;
/*        $res = mysql_query($query);  
        return mysql_fetch_assoc($res);    */    
    }    
}

    $db = new dataBase();
    $db -> mysqlAssoc("SELECT * FROM `table`");

Все достаточно пристойно, как водится, в конструкторе осуществляется подключение (коннект) к серверу баз данных, дальше работаем с нужными методами.
Однако если разработчиков несколько, либо имеются провалы в памяти, не исключена ситуация, что объект будет инициализирован несколько раз в разных местах. Там, где потребуется работа с базой данных. Это приведет к тому, что конструктор тоже сработает несколько раз. А значит и соединений с сервером баз данных получится несколько, а это лишние телодвижения, перерасход памяти и неоправданные тормоза:
<?php

class dataBase 
{

    public function __construct() 
    {
        echo 'Тут как бы коннект к базе<br>';
/*        mysql_connect( DBSERVER, DBUSER, DBPASSWORD );  
        mysql_select_db( DATABASE ); */        
    }
    
    public function mysqlAssoc($query) 
    {
        echo 'А тут как бы запрос: '. $query .'<br>';
/*        $res = mysql_query($query);  
        return mysql_fetch_assoc($res);    */    
    }    
}

    $db = new dataBase();
    $db -> mysqlAssoc("SELECT * FROM `table`");
    
    $db = new dataBase();
    $db -> mysqlAssoc("SELECT * FROM `table`");    
    
    $db = new dataBase();
    $db -> mysqlAssoc("SELECT * FROM `table`");
/*
         РЕЗУЛЬТАТ
    
Тут как бы коннект к базе
А тут как бы запрос: SELECT * FROM `table`
Тут как бы коннект к базе
А тут как бы запрос: SELECT * FROM `table`
Тут как бы коннект к базе
А тут как бы запрос: SELECT * FROM `table`
*/ 

Чтобы этого избежать, придуман хитрый способ - паттерн синглетон (Singleton). В переводе с английского - одиночка.
Вот мы в класс поместим статическую переменную $instance, а в неё метод getInstance() засунет экземпляр нашего класса.
Теперь этот экземпляр можно получить двумя способами, классическим и из статической переменной:
<?php

class dataBase 
{
/*
* В эту переменную мы поместим единственный
* экземпляр класса, с которым и будем работать дальше.
*/
    static private $instance;

/*
* А это метод, который и осуществит сие действо
*/
    public static function getInstance() 
    {
        self::$instance = new self;
        return self::$instance;
    }
    
/*        
* Ну а тут прочая белибирда
*/
    public function __construct() 
    {
        echo 'Тут как бы коннект к базе<br>';
/*        mysql_connect( DBSERVER, DBUSER, DBPASSWORD );  
        mysql_select_db( DATABASE ); */        
    }
    
    public function mysqlAssoc($query) 
    {
        echo 'А тут как бы запрос: '. $query .'<br>';
/*        $res = mysql_query($query);  
        return mysql_fetch_assoc($res);    */    
    }    
}

// Теперь объект можно получить так:
    $db = new dataBase();
    $db -> mysqlAssoc("SELECT * FROM `table`");    

// Или таким извратным способом:
    $db = dataBase::getInstance();;
    $db -> mysqlAssoc("SELECT * FROM `table`");
    
/*
         РЕЗУЛЬТАТ
    
Тут как бы коннект к базе
А тут как бы запрос: SELECT * FROM `table`
Тут как бы коннект к базе
А тут как бы запрос: SELECT * FROM `table`
*/

Обратите внимание, что инициализировали мы его ключевым словом self
//Это 
self::$instance = new self;

//тоже самое, что это
self::$instance = new dataBase();

На самом деле это не придает читабельности, дело на любителя. Просто нужно знать, что так можно.

Но вернемся к овину с нашими баранами. Для чего нужен такой выкрутас? А вот для чего. Если мы теперь в классе проверим на пустоту переменную $instance, то сможем узнать, вызывался ли этот класс методом getInstance(). И если уже вызывался, сделать оргвыводы и не дать вызвать его еще раз, выдав экземпляр из этой переменной.
А чтобы не дать вызвать класс обычным способом, объявим коструктор приватным. Тоесть запретим доступ к нему снаружи. Хватит того, что мы внутри класса уже набарагозили. Ну и таким же способом запретим клонирование на всякий случай.
<?php

class dataBase 
{
/*
* В эту переменную мы поместим единственный
* экземпляр класса, с которым и будем работать дальше.
*/
    static private $instance;

/*
* А это метод, который и осуществит сие действо
* за одно проверив существование объекта.
*/
    public static function getInstance() 
    {
        if(empty(self::$instance))  
            self::$instance = new self;
        
        return self::$instance;
    }
    
/*        
* Ну а тут прочая белибирда с приватным конструктором
*/
    private function __construct() 
    {
        echo 'Тут как бы коннект к базе<br>';
/*        mysql_connect( DBSERVER, DBUSER, DBPASSWORD );  
        mysql_select_db( DATABASE ); */        
    }
    
    public function mysqlAssoc($query) 
    {
        echo 'А тут как бы запрос: '. $query .'<br>';
/*        $res = mysql_query($query);  
        return mysql_fetch_assoc($res);    */    
    }
    
    private function __clone() 
    {
        /* Фиг вам. */    
    }        
}

// Теперь получить объект можно только извратным способом:
    $db = dataBase::getInstance();;
    $db -> mysqlAssoc("SELECT * FROM `table`");
    
// Несколько раз подряд
    $db = dataBase::getInstance();;
    $db -> mysqlAssoc("SELECT * FROM `table`");
    
// А тут будет ошибка
    $db = new dataBase();
    $db -> mysqlAssoc("SELECT * FROM `table`");    
    
/*
         РЕЗУЛЬТАТ
    
Тут как бы коннект к базе
А тут как бы запрос: SELECT * FROM `table`
А тут как бы запрос: SELECT * FROM `table`

Fatal error: Call to private dataBase::__construct() from invalid context
*/

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

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

Фабрика (Factory)
Этот паттерн можно понять на примере обработки изображений c помощью библиотеки GD.

Не секрет, что для обработки разных форматов (.gif, .jpg, .png) используются разные функции. Это значит нам нужно сначала определить формат, а потом применить нужную функцию. Многие делают это с помощью конструкции if... else, другие с помощью переключаиеля switch:
<?php

    function imageCreateForm($path)
    {    
        
        $type = pathinfo($path, PATHINFO_EXTENSION);
        
        switch ($type)
        {
            case 'gif':
                return imagecreatefromgif($path);
            break;
        
            case 'jpeg':
                return imagecreatefromjpeg($path);
            break;
            
            case 'png':
                return imagecreatefrompng($path);
            break;
        }                
    }

А если обратить внимание на название функций, можно заметить, что отличаются они только окончанием. И это окончание совпадает с типом картинки. По этому функцию можно переписать так:
<?php

    function imageCreateForm($path)
    {            
        $type = pathinfo($path, PATHINFO_EXTENSION);
        $function = 'imagecreatefrom'. $type;
        return $function($path);            
    }

Вот это и есть суть паттерна "Фабрика" (Factory) на низком уровне. То есть по какому то признаку мы определяем нужную функцию, и она вступает в дело. Не нужно переписывать повторяющийся код. В данном случае это не критично, но часто бывает довольно много дублирующегося кода и такой подход оправдан.

В случае ООП фабрика обычно штампует не функции, а объекты. Нам нужно знать только название. Мы уже проходили автолоад, вот функционал фабрики чем то на него похож. Только вызывается не сам класс, а класс фабрики. И по переданному в него параметру он выдает нам желаемый объект:
<?php

class Factory
{
    public static function loadClass($classname)
    {
       include_once $classname .'.php'; 
       return new $classname;
    }
}

    $obj = Factory::loadClass('Example');

Правда толку с такой реализации чуть меньше чем никакого, автолоад справляется с этой задачей куда лучше. Оправдано может быть только применение различных библиотек, которые находятся в разных каталогах. Тогда можно вторым параметром передать название папки, где его искать. В случае с автолоадом этого не выйдет:
<?php

class Factory
{
    public static function loadClass($classname, $dir)
    {
       include_once $dir .'/'. $classname .'.php'; 
       return new $classname;
    }
}

    $obj = Factory::loadClass('Example', 'classes');

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

Допустим у нас есть два класса по половому признаку. Эм и Жо. В зависимости от того, кто посетил наше богоугодное заведение, мы должны выдать необходимый функционал.
<?php

/*
* Это для мужчин
*/
class WC_Men_s
{
    public function __construct()
    {
        echo 'Ууу, еееее!<br>';
    }

}
/*
* Это для женщин
*/
class WC_Women_s
{
    public function __construct()
    {
        echo 'Хи-хи<br>';
    }

}

/*
* А тут определим, кому что нужно
*/
class Factory
{
    public function __construct($sex)      
    {
        if($sex == 'men')
            return new WC_Men_s();
        elseif($sex == 'women')
            return new WC_Women_s();
        else
            exit('Ахтунг!');
    }
}


$obj = new Factory('men');
$obj = new Factory('women');

///////////////////////////////////
/*       РЕЗУЛЬТАТ

Ууу, еееее!
Хи-хи

*/

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

Особенно удобно это бывает, когда приложение строится на более высоких уровнях абстракции. Тогда, имея один большой базовый класс, можно через фабрику генерировать объекты наследников, переопределяя необходимые методы.
<?php

/*
* Большой общий ту... класс.
*/
abstract class WC_Room
{
    
    abstract protected function defecation();     

    public function washHands()
    {
        return 'Буль буль<br>';
    }
    /* Другие разные методы */
   
}

/*
* Это для мужчин
*/
class WC_Men_s extends WC_Room
{
    public function defecation()
    {
        return 'Ууу, еееее!<br>';
    }

}

/*
* Это для женщин
*/
class WC_Women_s extends WC_Room
{
    public function defecation()
    {
        return 'Хи-хи<br>';
    }

}

/*
* А тут определим, кому что нужно
*/
class Factory
{
    static public function createObj($sex)
    {
        if($sex == 'men')
            return new WC_Men_s();
        elseif($sex == 'women')
            return new WC_Women_s();
        else
            exit('Ахтунг!');
    }
}


$obj = Factory::createObj('men');
echo $obj -> defecation();
echo $obj -> washHands();

$obj = Factory::createObj('women');
echo $obj -> defecation();
echo $obj -> washHands();

///////////////////////////////////
/*       РЕЗУЛЬТАТ

Ууу, еееее!
Буль буль
Хи-хи
Буль буль

*/

Теперь для того, чтобы по какому то признаку совершить нужные действия, мы через фабрику генерируем объект наследника, переопределяя нужные методы. А те методы, которые не нужно переопределять, автоматически становятся доступными. А сам признак можно задать динамически, получив к примеру из формы. Количество кода в таком случае резко сокращается, хотя прозрачности не прибавляет:
$sex = 'men';
//$sex = 'women';

$obj = Factory::createObj($sex);
echo $obj -> defecation();
echo $obj -> washHands();

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

Это уже больше подходит под определение "Абстрактная фабрика". Но определения не столь важны, важнее понять суть процессов, а реализация уже дело фантазии.
Теги:
Паттерны проектирования
Добавлено: 15 Августа 2013 11:38:38 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...