Пул данных

Кирилл Евсеев, May 30, 2011

Всем привет. Сегодня мы поговорим о пуле данных. Под словосочетанием “пул данных” частенько понимаются принципиально разные вещи. Например, система, которая имитирует пользовательский ввод данных и используется для автоматического тестирования какой-либо другой системы. Я же буду подразумевать под пулом данных некоторую программно-алгоритмическую структуру, предназначенную для хранения данных и работы с ними.

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

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

Допустим, есть набор некоторых данных и несколько хорошо задокументированных классов.

$data = array();

$data['a'] = 1;
$data['b'] = 2;
$data['c'] = 3;
$data['d'] = 4;


class A
{
    private $a = 0;
    private $b = 0;
    
    public function __construct($a, $b)
    {
        $this->a = $a;
        $this->b = $b;
    }
    
    public function process_data()
    {
        //.......
    }
    
    public function return_data()
    {
        $arr = array();
        
        $arr['a'] = $this->a;
        $arr['b'] = $this->b;
        
        return $arr;
    }
}

class B
{
    private $arr = array();
    
    public function __construct($a, $b)
    {
        $this->arr['a'] = $a;
        $this->arr['b'] = $b;
    }
    
    public function process_data()
    {
        //.......
    }
    
    public function return_a()
    {
        return $this->arr['a'];
    }

    public function return_b()
    {
        return $this->arr['b'];
    }

}


class C
{
    private $arr = array();
    
    public function __construct($arr)
    {
        $array = array();
        
        $array[0]['c'] = $arr['c'];
        $array[1]['d'] = $arr['d'];
        
        $this->arr[0]['a'] = $arr['a'];
        $this->arr[0]['b'] = $arr['b'];
        $this->arr[1]['xxx'] = $array;
        
    }
    
    public function process_data()
    {
        //.......
    }
    
    public function return_data()
    {
        return $this->arr;
    }
}

Ну как, осознали? А теперь представим, что у нас есть таких классов не три, а триста. Все эти классы работают абсолютно нормально. Они все получают некоторый набор данных, обрабатывают его и возвращают результаты. Результаты могут содержать уже изменённые данные, могут содержать первоначальные данные, а могут вообще не содержать никаких данных. Точно та же картина и при инициализации этих классов – данные всё те же, а форматы инициализации объектов разные.
В реальной программе такое может произойти, например, с личными данными пользователя. Может существовать много классов, которым нужно использовать данные пользователя. Какой-то класс запросит все данные. Какому-то будет достаточно только электронного адреса и он запросит в конструкторе инициализацию объекта строковой переменной. Одно дело, если вы в существующей системе создаёте новый класс, берёте данные из базы и как-то их обрабатываете. Совсем другое, когда вы вынуждены пользоваться уже обработанными данными и возникает ситуация, когда из нескольких объектов нужно получить одни и те же данные, и у этих данных разный формат. Конечно же вы получите, обработаете и вернёте данные. Снова в новом формате. И так до бесконечности. Рано или поздно, поддерживать систему станет практически невозможно. Даже если всё отлично задокументировано.

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

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

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

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

В-четвёртых, нужно чётко понимать, что пул данных – это не мусорка, а поэтому не стоит хранить в нём временные переменные. Например, такое использование пула данных является недопустимым –

class A
{
    private function foo()
    {
        $a = 5;
        $data_pool->set_data($a);
    }
    
    private function bar()
    {
        $a = $data_pool->get_data('a');
	//$a == 5;
    }
}

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

Кроме этого, пул данных может содержать массу других особенностей, зависящих от конкретного проекта, а именно – мы можем ввести систему приоритетов и запретить чтение и/или запись данных объектам, которые не могут иметь доступа к этим данным; мы можем хранить данные в базе, в файле или в массиве; мы можем сделать пул объектом, а можем предоставить несколько глобальных функций. В общем, архитектура пула зависит от вашей задачи.

Но хватит слов и перейдём к делу. Давайте разработаем простенький пул данных, который будет иметь уровни доступа и предоставлять пользователю операции записи и чтения.

Итак, у меня получилось следующее:

class data_pool
{
    private $data_pool=array();

    public function __construct($par=array())
    {
//инициализируем пул данных
        $this->init($par);
    }

    private function init($par=array())
    {

//мы не хотим давать высокоуровневой бизнес логике работать с
//низкоуровневыми данными поэтому мы сохраняем низкоуровневые
//данные в пул в реальной задаче эта часть должна быть
//вынесена из пула в такое место, где в т.ч. можно проверить,
//провалидировать и верифицировать эти низкоуровневые данные,
//получаемые приложением от пользователя
        isset($_SERVER) ? $server_arr = $_SERVER
                        : $server_arr = array();

        isset($_REQUEST)?  $request_arr = $_REQUEST
                        : $request_arr = array();
        
        isset($_GET) ? $get_arr = $_GET
                        : $get_arr = array();
                        
        isset($_POST) ? $post_arr = $_POST
                        : $post_arr = array();
                        
        isset($_COOKIE) ? $cookie_arr = $_COOKIE
                        : $cookie_arr = array();
                        
        isset($_SESSION) ? $session_arr = $_SESSION
                        : $session_arr = array();
        
        isset($_FILES) ? $files_arr = $_FILES
                        : $files_arr = array();


//записываем в пул системные данные
//теперь работа с низкоуровневыми данными будет
//осуществляться через объект пула четвёртый параметр 0 - это
//уровень доступа. т.о. мы запрещаем эти параметры
//перезаписывать
        $this->set_vars($server_arr, '_server',  0);
        $this->set_vars($request_arr,  '_request', 0);
        $this->set_vars($get_arr, '_get', 0);
        $this->set_vars($post_arr, '_post', 0);
        $this->set_vars($cookie_arr, '_cookie', 0);
        $this->set_vars($files_arr, '_files', 0);

//пятый параметр 1 обозначает данные сессии. см. ниже 
        $this->set_vars($session_arr, '_session', 1, 1);
        

//записываем в пул данные, которыми был инициализирован объект
//под алиасом мы понимаем владельца переменной. см. ниже        
        if(isset($par['data_pool'])&&!empty($par['data_pool'])
                               && is_array($par['data_pool']))
        {
            foreach($par['data_pool'] as $alias=>$var)
            {
                $this->set_vars($var, $alias, 0, 0);
            }
        }
    }



//этот метод доступен в любой точке программы и позволяет
//записать переменную в пул данных.
    public function set_var($val=null, $var='',
                        $alias='_local', $access=1,$session=0)
    {
//$val - значение
//$var - имя переменной
//$alias - алиас, т.е. владелец переменной. переменная с
//алиасом _local - это просто обычная переменная, которая не
//зависит от владельца и может использоваться в любой точке
//программы пример такой переменной -  username, password,
//calculation_result и т.п.
//$access - доступ. я использую самый примитивный тип
//доступа: 0 - нельзя изменять значение, 1 - можно
//вы можете разработать любую систему привилегий и доступа.
//$session - 1, если мы хотим сохранить переменную не только
//в пуле, но ещё и в сессии. тогда при переходе на другую
//страницу, переменная останется в сессии и пуле и не
//потеряется. 

        $success = false;

        if(isset($var)&&!empty($var))
        {
            if(empty($alias))
            {
                $alias='_local';
            }

//предотвращаем перезапись значения в пуле, если значение
//$access == 0. для этого сравниваем первый бит с нулём
//логическая операция & 

            if(isset($this->data_pool[$alias][$var]['access'])
            && ($this->data_pool[$alias][$var]['access'] & 0))
            {
                $success = false;
            }
            else
            {

//записываем переменную в пул и возвращаем булевский
//результат записи
                $success = $this->set($val,
                            $var, $alias, $access, $session);
            }
        }

        return $success;
    }


    private function set($val, $var,
                        $alias='_local',$access=1,$session=0)
    {
        $success = false;
        
        if(isset($var)&&!empty($var))
        {
            $var_arr = array();

//устанавливаем алиас по дефолту
            if(empty($alias))
            {
                $alias='_local';
            }

//сохраняем значение в пуле данных
            $this->data_pool[$alias][$var]['value']=$val;
            $this->data_pool[$alias][$var]['access']=$access;

//сохраняем переменную в сессию. 
            if(isset($session)&&$session==1)
            {
                $_SESSION[$alias][$var]['value']= $val;
                $_SESSION[$alias][$var]['access']= $access;
            }

            $success=true;
        }
        
        return $success;
    }


    public function set_vars($vars=array(),
                    $alias='_local', $access=1, $session=0)
    {

//набор параметров тот же, за исключением первых двух.
//этот метод принимает массив и сохраняет в пул не одиночное
//значение, а набор данных
 
        $success = false;
        
        if(isset($vars)&&!empty($vars)&&is_array($vars))
        {
            $ret = true;
            
            foreach($vars as $k=>$v)
            {

                $success = $this->set_var($v, $k,
                                  $alias, $access, $session);
                
                if($success==false)
                {
                    $ret = false;
                }
            }

//если хотя бы одна переменная из сохраняемого массива была
//записана в пул неправильно возвращаем false
            if($ret == false)
            {
                $success = false;
            }
        }

        return $success;
    }

//проверяем наличие переменной в пуле
    public function isset_var($var,$alias='_local')
    {
        if(empty($alias))
        {
            $alias='_local';
        }
        
        return isset($this->data_pool[$alias][$var]);
    }

//проверяем наличие переменных в пуле по указанному алиасу
    public function isset_vars($alias='_local')
    {
        if(empty($alias))
        {
            $alias='_local';
        }
        
        return isset($this->data_pool[$alias]);
    }

//получаем переменную по её имени
    public function get_var($var, $alias='_local')
    {
        $val=null;
        
        if(empty($alias))
        {
            $alias='_local';
        }
        
        if(isset($this->data_pool[$alias][$var]['value']))
        {
            $val = $this->data_pool[$alias][$var]['value'];
        }

        return $val;
    }

//получаем переменные по алиасу
    public function get_vars($alias='_local')
    {
        $vals = null;
        
        if(empty($alias))
        {
            $alias='_local';
        }
        
        if(isset($this->data_pool[$alias])&&
                            !empty($this->data_pool[$alias]))
        {
            $vals = array();

            foreach($this->data_pool[$alias] as $k=>$v)
            {
                $vals[$k]=$v['value'];
            }
        }

        return $vals;
    }

//возвращаем весь пул
    public function get_vars_all()
    {
        return $this->data_pool;
    }
}

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

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

Надеюсь, вы сегодня узнали что-то новое и интересное.

Всем удачи.

В тему:

1комментарий

Почему бы тебе не отделить определение методов класса от их реализации ? Сначала человеку требуется узнать, ЧТО делает класс, а потом уже КАК. По твоему листингу это понять очень затруднительно.

Владимир, July 28, 2011 2:36 pm Reply
Ваше имя
Ваш email*
Ваш сайт
Текст вашего комментария:

Поиск по блогу:
Подписаться:
Популярные:
Облако тегов:
Разное:
Счетчик: