Легковес / Приспособленец (Flyweight) — шаблон проектирования
26 января 2020
Шаблон предлагает оптимизацию использования объектов, чтобы снизить затраты оперативной памяти и процессора. Оптимизация заключается в вынесении уникальных свойств объектов за пределы объекта, уменьшении количества объектов и построении конструкций, когда клиентский код использует объекты повторно.
Порядок оптимизации:
Пожалуй, самым популярным применением паттерна Легковеса являются таблицы каскадных стилей (CSS), где в качестве Легковеса выступает CSS-файл, а оптимизированными объектами выступают CSS-классы. Ведь цель каждого класса состоит в группировке стилей, применяемых для оформления нескольких элементов страницы. Чем компактнее сделаны CSS-классы, тем быстрее браузер отрисовывает HTML-страницу.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)
Результат работы скрипта test_before.php
Как можно увидеть по коду, в момент обхода каждой статьи происходит получение сведений об авторе с помощью функции getAuthorFromDB($id), которая находится в файле libs.php. Минус такого способа в том, что в случае вывода объёмного количества статей, например 500 штук, в базу данных будет направлено 500 запросов.
Для оптимизации получения авторов применим шаблон Легковеса.
Результат работы скрипта test_after.php
Порядок оптимизации:
- Допустим, мы используем набор однотипных объектов. В этом наборе проводим изучение свойств объектов и их значений — смотрим, какие свойства содержат одинаковые значения, а какие разные;
- Свойства с разными значениями мы выносим за пределы объектов — такие свойства становятся внешними. А свойства с одинаковыми значениями мы не трогаем, оставляем как есть — они являются внутренними. В итоге получаем несколько групп одинаковых объектов;
- Среди одинаковых объектов оставляем один, а остальные удаляем. Тем самым сокращается количество объектов, снижаются расходы оперативной памяти и процессорного времени;
- Клиентский код теперь работает с меньшим количеством объектов и при необходимости дополняет объекты внешними свойствами.
Пожалуй, самым популярным применением паттерна Легковеса являются таблицы каскадных стилей (CSS), где в качестве Легковеса выступает CSS-файл, а оптимизированными объектами выступают CSS-классы. Ведь цель каждого класса состоит в группировке стилей, применяемых для оформления нескольких элементов страницы. Чем компактнее сделаны CSS-классы, тем быстрее браузер отрисовывает HTML-страницу.
Пример задачи: в списке статей вывести имя автора.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)

libs.php
Содержит функцию getAuthorFromDB($id), которая имитирует запрос сведений об авторе из базы данных.<?
/**
* Получает сведения об авторе из БД.
* Это тестовая функция, эмулирующая процесс запроса данных из БД.
*
* @param int $id - ID автора
* @return array|bool - вернёт массив в случае успеха или 'false' в случае ошибки
*/
function getAuthorFromDB($id)
{
$listAuthors = array(
453 => array(
'name' => 'Иванов Иван',
'photo' => '/pics/i_ivanov.jpg',
),
18 => array(
'name' => 'Пётр Свиридов',
'photo' => '/pics/p_sviridov.jpg',
),
975 => array(
'name' => 'Мария Кравцова',
'photo' => '/pics/mari.jpg',
),
97 => array(
'name' => 'Семён Лавров',
'photo' => '/pics/sema.jpg',
),
5 => array(
'name' => 'Ангелина Маликова',
'photo' => '/pics/angelina.jpg',
),
);
if (isset($listAuthors[$id])) {
return $listAuthors[$id];
} else {
return false;
}
}
?>
test_before.php
Скрипт до оптимизации, в котором реализован обход статей и получение имени автора отдельным запросом. Запросы в БД здесь виртуальные, чтобы читатель мог запустить пример без развёртывания и наполнения таблиц реальной базы данных.<?
//Массив статей
$articles = array(
array(
'id' => 1,
'title' => 'Тестовая статья 1',
'author' => 18,
'date' => '2019-12-01',
),
array(
'id' => 2,
'title' => 'Тестовая статья 2',
'author' => 97,
'date' => '2019-12-02',
),
array(
'id' => 3,
'title' => 'Тестовая статья 3',
'author' => 18,
'date' => '2019-12-03',
),
array(
'id' => 4,
'title' => 'Тестовая статья 4',
'author' => 5,
'date' => '2019-12-04',
),
array(
'id' => 5,
'title' => 'Тестовая статья 5',
'author' => 975,
'date' => '2019-12-05',
),
array(
'id' => 6,
'title' => 'Тестовая статья 6',
'author' => 18,
'date' => '2019-12-06',
),
);
//Подключаем библиотеку функций
require_once(__DIR__.'/libs.php');
//Обходим статьи
$contentList = '';
foreach ($articles as $item) {
//Сведения об авторе
$authorBlock = '';
$author = getAuthorFromDB($item['author']);
if (is_array($author)) {
$authorBlock = '<b>'.htmlspecialchars($author['name']).'</b><br>';
}
$contentList .= '
<div>
'.$authorBlock.
htmlspecialchars($item['title']).'
</div>
<br>
';
}
//Выводим список статей
echo $contentList;
?>
Результат работы скрипта test_before.php
Пётр Свиридов
Тестовая статья 1
Семён Лавров
Тестовая статья 2
Пётр Свиридов
Тестовая статья 3
Ангелина Маликова
Тестовая статья 4
Мария Кравцова
Тестовая статья 5
Пётр Свиридов
Тестовая статья 6
Как можно увидеть по коду, в момент обхода каждой статьи происходит получение сведений об авторе с помощью функции getAuthorFromDB($id), которая находится в файле libs.php. Минус такого способа в том, что в случае вывода объёмного количества статей, например 500 штук, в базу данных будет направлено 500 запросов.
Для оптимизации получения авторов применим шаблон Легковеса.
authors.class.php
Представляет класс Authors для работы с авторами статей. Класс является шаблоном Легковеса и состоит из следующих частей:- Свойство authors — хранит набор авторов в виде массива. Ключом каждого элемента массива является ID автора. Это сделано, чтобы по ID можно было быстро обратиться к сведениям об авторе;
- Метод getById($id) — возвращает сведения об авторе по ID. Сначала метод смотрит наличие автора в свойстве authors. Если его там нет, то метод запрашивает данные из базы данных и затем добавляет автора в свойство authors. Тем самым мы сокращаем количество обращений к БД.
<?
//Класс авторов статей
class Authors
{
/**
* Массив с авторами
* @var array
*/
private static $authors = array();
/**
* Возвращает сведения об авторе
* @param int - ID автора
* @return array|bool - в случае успеха вернёт массив со сведениями об авторе, а в случае ошибки вернёт 'false'
*/
public function getById($id)
{
//Приведение типов
$id = (int)$id;
if (isset(self::$authors[$id])) {
return self::$authors[$id];
} else {
//Подключаем библиотеку функций
require_once(__DIR__.'/libs.php');
//Получаем запись автора из БД
$author = getAuthorFromDB($id);
if (is_array($author)) {
self::$authors[$id] = $author;
return self::$authors[$id];
} else {
return false;
}
}
}
}
?>
test_after.php
В скрипте всё также содержится обход статей, только сведения об авторе мы уже получаем с помощью метода getById($id) класса Authors. Если в статьях будут попадаться одинаковые авторы, то класс не станет делать повторные запросы в базу данных, а возьмёт данные из свойства authors. Таким образом, несколько статей будут обращаться к одному и тому же элементу автора и участку памяти. Этого мы и стремились, применив шаблон Легковеса и оптимизировав использование ресурсов сервера.<?
//Массив статей
$articles = array(
array(
'id' => 1,
'title' => 'Тестовая статья 1',
'author' => 18,
'date' => '2019-12-01',
),
array(
'id' => 2,
'title' => 'Тестовая статья 2',
'author' => 97,
'date' => '2019-12-02',
),
array(
'id' => 3,
'title' => 'Тестовая статья 3',
'author' => 18,
'date' => '2019-12-03',
),
array(
'id' => 4,
'title' => 'Тестовая статья 4',
'author' => 5,
'date' => '2019-12-04',
),
array(
'id' => 5,
'title' => 'Тестовая статья 5',
'author' => 975,
'date' => '2019-12-05',
),
array(
'id' => 6,
'title' => 'Тестовая статья 6',
'author' => 18,
'date' => '2019-12-06',
),
);
//Подключаем класс для работы с авторами статей
require_once(__DIR__.'/authors.class.php');
//Обходим статьи
$contentList = '';
foreach ($articles as $item) {
//Сведения об авторе
$authorBlock = '';
$author = Authors::getById($item['author']);
if (is_array($author)) {
$authorBlock = '<b>'.htmlspecialchars($author['name']).'</b><br>';
}
$contentList .= '
<div>
'.$authorBlock.
htmlspecialchars($item['title']).'
</div>
<br>
';
}
//Выводим список статей
echo $contentList;
?>
Результат работы скрипта test_after.php
Пётр Свиридов
Тестовая статья 1
Семён Лавров
Тестовая статья 2
Пётр Свиридов
Тестовая статья 3
Ангелина Маликова
Тестовая статья 4
Мария Кравцова
Тестовая статья 5
Пётр Свиридов
Тестовая статья 6