Строитель (Builder) — шаблон проектирования
18 января 2020
Шаблон предлагает схему пошагового создания сложных объектов. С помощью паттерна, используя один и тот же конструктивный код, можно создавать объекты разных типов или одного типа, но с разным наполнением.
Под сложным объектом понимается объект с заполненными свойствами и разветлённой логикой поведения, зависящей от этих свойств и контекста. Чтобы вынести заполнение свойств за пределы пользовательского кода или же облегчить заполнение свойств с пользовательской стороны, то используется класс-строитель.
В паттерне фигурируют следующие понятия:
Далее приводится реализация паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)
Обратите внимание, что базы MySQL и MS SQL имеют различия в синтаксисте оператора SELECT. Например, запрос на выборку 10 элементов таблицы table1 в MySQL будет выглядеть так:
А для MS SQL запрос выглядит следующим образом:
Так же файл содержит, наследованные от абстрактного строителя BuilderSQLQuery, классы строителей BuilderSQLQueryMySQL (для построения запроса к MySQL) и BuilderSQLQueryMSSQL (для построения запроса к MS SQL).
Как можно увидеть, порядок работы с MySQL и MS SQL у строителей BuilderSQLQueryMySQL и BuilderSQLQueryMSSQL одинаковый.
Результат работы скрипта test.php
А где же директор? В тестовом скрипте роль директора выполняет пользовательский код, который поочерёдно вызывает методы select, from, where, order, count, заполняя свойства продукта, и затем с помощью getQuery возвращается объект продукта:
Так как sql-запросы обычно разные и редко повторяются в разных местах проекта, то для данной задачи нет необходимости создавать отдельный класс директора, чтобы вынести поэтапную сборку продукта строителем в какую-то логическую сущность. Но если указанный запрос на получение 50 клиентов старше 18 лет используется в нескольких местах программы и чтобы каждый раз в пользовательском коде не повторять длинные конструкции, то есть смысл добавить отдельный класс директора, который внутри себя будет поэтапно вызывать методы строителя select, from, where, order, count и возвращать объект запроса. В итоге создание запроса примет вид:
Код метода getClients18AndMore
Пример задачи: создание запросов к БД, используя конструктор запросов.
Под сложным объектом понимается объект с заполненными свойствами и разветлённой логикой поведения, зависящей от этих свойств и контекста. Чтобы вынести заполнение свойств за пределы пользовательского кода или же облегчить заполнение свойств с пользовательской стороны, то используется класс-строитель.
В паттерне фигурируют следующие понятия:
- Строитель – класс, который инкапсулирует внутри себя пошаговое создание продукта. На каждом шаге в будущий продукт добавляется какая-то значимая часть, а в конце производится финишная сборка продукта;
- Директор – класс, который управляет строителем. Директор требуется в тех случаях, когда нужно перенести процесс поэтапной сборки продукта строителем в какую-то логическую сущность. В иных случаях роль директора выполняет пользовательский код;
- Продукт – то, что создаёт строитель. Это может быть объект класса, строка, число или переменная любого другого типа данных.
Далее приводится реализация паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)
sql_classes.php
Содержит классы для формирования запросов в базы данных:- SQLQuery — абстрактный класс для работы с запросами абстрактной реляционной базы данных;
- SQLQueryMYSQL — конечный класс для работы с запросами БД MySQL. Представляет собой продукт строителя;
- SQLQueryMSSQL — конечный класс для работы с запросами БД MS SQL. Представляет собой продукт строителя.
<?php
/**
* Абстрактный класс для создания запроса в произвольную БД
*/
abstract class SQLQuery
{
/**
* Операция запроса. Допускаются значения: 'SELECT', 'INSERT', 'UPDATE', 'DELETE'
* @var string
*/
public $operation;
/**
* Название таблицы, в отношении которой выполняется запрос
* @var string
*/
public $table;
/**
* Перечень столбцов, в отношении которых выполняется запрос
* @var string
*/
public $columns;
/**
* Условия запроса
* @var string
*/
public $where;
/**
* Параметры сортировки
* @var string
*/
public $order;
/**
* Количество элементов, подпадающих под действие запроса.
* Например, количество элементов попадающих в выборку SELECT
* @var int
*/
public $countElements;
public function __construct()
{
$this->reset();
}
/**
* Очищает свойства объекта запроса, подгатавливая его для нового запроса
*/
public function reset()
{
$this->operation = '';
$this->table = '';
$this->columns = '';
$this->where = '';
$this->order = '';
$this->countElements = 0;
}
/**
* Получение строки с готовым запросом
* @return string
*/
abstract public function getString();
}
/**
* Класс для создания запроса в БД MySQL
*/
class SQLQueryMYSQL extends SQLQuery
{
/**
* Получение строки с готовым запросом
* @return string
*/
public function getString()
{
$query = '';
//Операция выборки SELECT
if ($this->operation == 'SELECT') {
$query = 'SELECT';
//Поля выборки
if ($this->columns == '') {
$query.= ' *';
} else {
$query.= ' '.$this->columns;
}
//Таблица
$query .= ' FROM '.$this->table;
//Условия
if ($this->where != '') {
$query .= ' WHERE '.$this->where;
}
//Параметры сортировки
if ($this->order != '') {
$query .= ' ORDER BY '.$this->order;
}
//Ограничитель количества элементов выборки
if ($this->countElements > 0) {
$query .= ' LIMIT 0, '.$this->countElements;
}
}
return $query;
}
}
/**
* Класс для создания запроса в БД MSSQL
*/
class SQLQueryMSSQL extends SQLQuery
{
/**
* Получение строки с готовым запросом
* @return string
*/
public function getString(): string
{
$query = '';
//Операция выборки SELECT
if ($this->operation == 'SELECT') {
$query = 'SELECT';
//Ограничитель выборки
if ($this->countElements > 0) {
$query .= ' TOP '.$this->countElements;
}
//Поля выборки
if ($this->columns == '') {
$query.= ' *';
} else {
$query.= ' '.$this->columns;
}
//Таблица
$query .= ' FROM '.$this->table;
//Условия
if ($this->where != '') {
$query .= ' WHERE '.$this->where;
}
//Параметры сортировки
if ($this->order != '') {
$query .= ' ORDER BY '.$this->order;
}
}
return $query;
}
}
?>
Обратите внимание, что базы MySQL и MS SQL имеют различия в синтаксисте оператора SELECT. Например, запрос на выборку 10 элементов таблицы table1 в MySQL будет выглядеть так:
SELECT * FROM table1 LIMIT 10
А для MS SQL запрос выглядит следующим образом:
SELECT TOP 10 * FROM table1
sql_builders.php
Содержит абстрактный класс строителя BuilderSQLQuery, который включает в себя:- Защищённое свойство $query – продукт строителя и он же объект запроса;
- Методы select, from, where, order, count для наполнения объекта $query различными параметрами. Обратите внимание, что каждый из данных методов возвращает объект текущего строителя return $this; Это сделано для того, чтобы методы можно было вызывать друг за другом;
- Метод reset для очистки параметров объекта запроса при формировании нового запроса;
- Метод getQuery для получения объекта запроса (продукта).
Так же файл содержит, наследованные от абстрактного строителя BuilderSQLQuery, классы строителей BuilderSQLQueryMySQL (для построения запроса к MySQL) и BuilderSQLQueryMSSQL (для построения запроса к MS SQL).
<?php
//Подключение классов запросов для работы с БД
require_once(__DIR__.'/sql_classes.php');
/**
* Абстрактный класс конструктора запросов к различным видам БД
*/
abstract class BuilderSQLQuery
{
/**
* Объект запроса
* @var SQLQuery
*/
protected $query;
/**
* Конструктор конструктора.
* В конечном классе тут создаётся объект запроса для конкретного вида БД
*/
public function __construct()
{
/*
Образец вызова (вместо SQLQuery укажите класс для работы с конкртеной БД):
$this->query = new SQLQuery();
*/
}
/**
* Возвращает объект запроса
* @return SQLQuery;
*/
public function getQuery()
{
return $this->query;
}
/**
* Устанавка операции выборки SELECT и указание колонок выборки
* @param string $columns - колонки, которые будут возвращены в выборке. Если указать пустое значение, то будут возвращены все колонки
* @return BuilderSQLQuery
*/
public function select($columns = '')
{
$this->query->operation = 'SELECT';
$this->query->columns = $columns;
return $this;
}
/**
* Установка таблицы, из которой происходит выборки
* @param string $table - таблица выборки
* @return BuilderSQLQuery
*/
public function from($table)
{
$this->query->table = $table;
return $this;
}
/**
* Установка условий запроса
* @param string $where - строка с условиями запроса
* @return BuilderSQLQuery
*/
public function where($where)
{
$this->query->where = $where;
return $this;
}
/**
* Установка параметров сортировки
* @param string $order - строка параметрами сортировки запроса
* @return BuilderSQLQuery
*/
public function order($order)
{
$this->query->order = $order;
return $this;
}
/**
* Установка количества элементов, подпадающих под действие запроса.
* Например, количество элементов попадающих в выборку SELECT
*
* @param int|string $count - количество элементов
* @return BuilderSQLQuery
*/
public function count($count)
{
$this->query->countElements = (int)$count;
return $this;
}
/**
* Очищает свойства объекта запроса, подгатавливая его для нового запроса
* @return BuilderSQLQuery
*/
public function reset()
{
$this->reset();
return $this;
}
}
/**
* Конструктор запроса в БД MySQL
*/
class BuilderSQLQueryMySQL extends BuilderSQLQuery
{
public function __construct()
{
$this->query = new SQLQueryMySQL();
}
}
/**
* Конструктор запроса в БД MSSQL
*/
class BuilderSQLQueryMSSQL extends BuilderSQLQuery
{
public function __construct()
{
$this->query = new SQLQueryMSSQL();
}
}
?>
test.php
Тестовый скрипт, в котором с помощью строителя BuilderSQLQueryMySQL выполняется создание запроса к MySQL и затем с помощью метода getQuery объект запроса помещается в переменную $queryMySql. По аналогии, но с помощью строителя BuilderSQLQueryMSSQL, происходит создание запроса к MS SQL. В конце файла, используя метод getString, получаем текст sql-запросов и выводим их на экран.Как можно увидеть, порядок работы с MySQL и MS SQL у строителей BuilderSQLQueryMySQL и BuilderSQLQueryMSSQL одинаковый.
<?php
//Подключение конструкторов запросов для работы с БД
require_once(__DIR__.'/sql_builders.php');
//Запрос в БД MySQL
$queryMySql = (new BuilderSQLQueryMySQL())
->select('id, name')
->from('clients')
->where('age >= 18')
->order('name ASC')
->count(50)
->getQuery();
//Запрос в БД MSSQL
$queryMSSql = (new BuilderSQLQueryMSSQL())
->select('id, name')
->from('clients')
->where('age >= 18')
->order('name ASC')
->count(50)
->getQuery();
$queryStr = $queryMySql->getString();
echo "Запрос на MySQL:\n".$queryStr."\n\n";
$queryStr = $queryMSSql->getString();
echo "Запрос на Microsoft SQL:\n".$queryStr."\n\n";
?>
Результат работы скрипта test.php
Запрос на MySQL:
SELECT id, name FROM clients WHERE age >= 18 ORDER BY name ASC LIMIT 0, 50
Запрос на Microsoft SQL:
SELECT TOP 50 id, name FROM clients WHERE age >= 18 ORDER BY name ASC
А где же директор? В тестовом скрипте роль директора выполняет пользовательский код, который поочерёдно вызывает методы select, from, where, order, count, заполняя свойства продукта, и затем с помощью getQuery возвращается объект продукта:
//Запрос в БД MSSQL
$queryMSSql = (new BuilderSQLQueryMySQL())
->select('id, name')
->from('clients')
->where('age >= 18')
->order('name ASC')
->count(50)
->getQuery();
Так как sql-запросы обычно разные и редко повторяются в разных местах проекта, то для данной задачи нет необходимости создавать отдельный класс директора, чтобы вынести поэтапную сборку продукта строителем в какую-то логическую сущность. Но если указанный запрос на получение 50 клиентов старше 18 лет используется в нескольких местах программы и чтобы каждый раз в пользовательском коде не повторять длинные конструкции, то есть смысл добавить отдельный класс директора, который внутри себя будет поэтапно вызывать методы строителя select, from, where, order, count и возвращать объект запроса. В итоге создание запроса примет вид:
$director = new DirectorBilderSQL();
$queryMSSql = $director->getClients18AndMore(new BuilderSQLQueryMSSQL(), 50);
Код метода getClients18AndMore
/**
* Возвращает объекта запроса на выборку клиентов, возрастом от 18 лет и старше
* @param BuilderSQLQuery $builderSQL - конструктор запроса (объект-строитель)
* @param int $count - максимальное количество клиентов, которые должны попасть в выборку
* @return SQLQuery - вернёт объект запроса
*/
public function getClients18AndMore($builderSQL, $count)
{
return $builderSQL->select('id, name')
->from('clients')
->where('age >= 18')
->order('name ASC')
->count(50)
->getQuery($count);
}