Пул данных
Всем привет. Сегодня мы поговорим о пуле данных. Под словосочетанием “пул данных” частенько понимаются принципиально разные вещи. Например, система, которая имитирует пользовательский ввод данных и используется для автоматического тестирования какой-либо другой системы. Я же буду подразумевать под пулом данных некоторую программно-алгоритмическую структуру, предназначенную для хранения данных и работы с ними.
Итак, зачем вообще нужен пул данных? Основной принцип разработки софта – модульность. То есть мы стремимся к тому, чтобы минимизировать зависимости между классами, чтобы наша программа была не монолитным куском кода, а набором некоторых подпрограмм, которые можно комбинировать друг с другом, добиваясь гибкости и расширяемости системы. Понятное дело, такой подход – результат эволюции. И его преимущества сложно переоценить. Хотя бы тот факт, что в монолитной программе исправление какого-нибудь участка кода может запросто привести к веерным изменениям по всему монолиту, в то время, как в хорошо продуманной модульной структуре любые изменения останутся локальными, главное только сохранить формат и логику входных и выходных параметров.
Проблема заключается в следующем – большую модульную систему могут разрабатывать не то, что разные отделы внутри одной компании, а и вообще разные компании. При этом, каждый модуль будет замечательно работать, но все эти модули будут работать с данными в разных форматах. Я сейчас объясню, что я имею ввиду. Считается, что системе достаточно быть хорошо задокументированной, чтобы избежать этих проблем. Фактически, происходит следующее.
Допустим, есть набор некоторых данных и несколько хорошо задокументированных классов.
$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; } }
Вот такой вот пул данных. На самом деле, в реальной задаче можно разработать ещё и проверку типа сохраняемой переменной. А так же, определённый формат для каждого алиаса, т.е. чтобы любой набор переменных под алиасом был всегда одного и того же вида. В общем, полёт фантазии программиста ограничивается только бюджетом его заказчика 🙂
Резюмируя, нужно сказать, что пул данных является стандартом де-факто для сложных веб-систем, которые пишутся более чем одним программистом.
Надеюсь, вы сегодня узнали что-то новое и интересное.
Всем удачи.
В тему:
Почему бы тебе не отделить определение методов класса от их реализации ? Сначала человеку требуется узнать, ЧТО делает класс, а потом уже КАК. По твоему листингу это понять очень затруднительно.
Владимир, July 28, 2011 2:36 pm