Мост (Bridge) — шаблон проектирования
21 января 2020
Шаблон предлагает разделить крупный монолитный класс на класс-сущность (абстракцию) и классы-функциональности (реализации функций). Класс-сущность содержит ссылки на объекты классов-функциональностей, таким образом ссылки выполняют роль моста, связывая сущность и её функциональность. Клиентский код преимущественно работает с объектом сущности. Данный подход позволяет заменить объёмный класс отдельными, более простыми и логически обособленными композициями, а также развивать их с меньшей зависимостью друг от друга.
Некоторая литература декларирует, что Мост позволяет изменять сущность и её функциональности независимо друг от друга, но это не совсем так и сбивает разработчика с толку. Зависимость снижается, но вовсе не пропадает — как минимум у сущности сохраняется зависимость от интерфейса функциональности. Поэтому нужно быть внимательным, внося изменения в объявления свойств и методов (то есть в интерфейс) класса-функциональности, которые используются сущностью.
Паттерн Мост немного похож на Адаптер, только здесь роль класса-адаптера выполняет класс-сущность и его дочерние классы.
Ниже приводится реализация паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)
Общая идея заключается в том, чтобы не писать один монструозный класс конвертера, а выделить управленческий класс, с которым будет работать клиентский код, затем выделить отдельные техническо-функциональные классы для конвертирования в конкретные форматы (например, для технической конвертации в JPG будет использоваться один класс, для PNG — другой класс) и связать их с управленческим классом.
Обратите внимание, что клиентский код работает исключительно с классами конвертеров (сущностями) и не касается классов медиаконтейнеров (функциональностей), хотя непосредственное конвертирование выполняется в медиаконтейнерах.
Результат работы скрипта test.php
Некоторая литература декларирует, что Мост позволяет изменять сущность и её функциональности независимо друг от друга, но это не совсем так и сбивает разработчика с толку. Зависимость снижается, но вовсе не пропадает — как минимум у сущности сохраняется зависимость от интерфейса функциональности. Поэтому нужно быть внимательным, внося изменения в объявления свойств и методов (то есть в интерфейс) класса-функциональности, которые используются сущностью.
Паттерн Мост немного похож на Адаптер, только здесь роль класса-адаптера выполняет класс-сущность и его дочерние классы.
Пример задачи: создание медиаконвертеров.
Ниже приводится реализация паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)
Общая идея заключается в том, чтобы не писать один монструозный класс конвертера, а выделить управленческий класс, с которым будет работать клиентский код, затем выделить отдельные техническо-функциональные классы для конвертирования в конкретные форматы (например, для технической конвертации в JPG будет использоваться один класс, для PNG — другой класс) и связать их с управленческим классом.

converters.classes.php
Cодержит классы конвертеров:- Converter — абстрактный класс конвертера. Содержит свойство mediaContainer, в котором хранится объект (ссылка на объект) медиаконтейнера. Именно это свойство является мостом, связывающим сущность (конвертер) с функциональностью (контейнером, выполняющего конвертацию файла в конкретный формат).
Также класс содержит абстрактный метод convert($pathFileFrom, $pathFileTo) для выполнения конвертации. Объявив метод, мы определили интерфейс класса, ведь именно с этим интерфейсом предстоит работать пользовательскому коду. - ConverterImage — класс для конвертации изображений. Здесь метод convert обрёл определение. Обратите внимание, что процесс конвертирования $this->mediaContainer->convert($pathFileFrom, $pathFileTo) выполняется через объект медиаконтейнера, находящегося в свойстве mediaContainer.
- ConverterAudio — класс для конвертации аудиофайлов. Метод convert здесь тоже обрёл определение.
<?
//Подключение классов медиаконтейнеров
require_once(__DIR__.'/media_containers.classes.php');
/**
* Абстрактный класс конвертера
*/
abstract class Converter
{
/**
* Медиаконтейнер. Отвечает за формат выходного файла и выполняет
* непосредственное конвертирование из файла-источника.
*
* @var MediaContainer
*/
protected $mediaContainer;
/**
* Массив разрешённых к конвертированию форматов файла-источника.
* Форматы файлов указываются в нижнем регистре (пример: 'mp3')
* @var string[]
*/
protected const VALID_SOURCE_FILE_FORMATS = array();
/**
* Конструктор объекта
* @param string $formatMediaContainer - формат выходного файла, для которого (формата) существует класс-медиаконтейнер.
* Например, если указать формат 'JPG', то должен существовать класс MediaContainerJPG.
* Имя класса выбирается путём соединения строки 'MediaContainer' и указанного формата (значения переменной $formatMediaContainer)
*/
public function __construct($formatMediaContainer)
{
$classMediaContainer = 'MediaContainer'.$formatMediaContainer;
$this->mediaContainer = new $classMediaContainer();
}
/**
* Выполняет конвертирование файла-источника в выходной файл
* @param string $pathFileFrom - путь к файлу-источнику
* @param string $pathFileTo - путь к выходному файлу
*
* @return string[] - вернёт массив со сведениями о результатах конвертации.
* В случае успеха элемент-ключ 'status' будет содержать значение 'success',
* а в случае ошибки будет содержать значение 'statusDesc' и в элементе-ключе 'statusDesc'
* будет содержаться описание ошибки.
*/
abstract public function convert($pathFileFrom, $pathFileTo);
/**
* Проверяет файл-источник на соотвествие разрешённых к конвертированию форматов
* @param string $pathFile - путь к файлу-источнику
* @return bool - вернёт 'true' в случае соотвествия и 'false' в противном случае
*/
public function sourceFileIsValid($pathFile)
{
if (count($this::VALID_SOURCE_FILE_FORMATS) == 0) {
return true;
}
$pathFile = trim(mb_strtolower($pathFile, 'utf-8'));
$fileFormat = end(explode('.', $pathFile));
if ($fileFormat == '' || (count($this::VALID_SOURCE_FILE_FORMATS) > 0 && in_array($fileFormat, $this::VALID_SOURCE_FILE_FORMATS) == false)) {
return false;
} else {
return true;
}
}
}
/**
* Конвертер картинок
*/
class ConverterImage extends Converter
{
/**
* Массив разрешённых к конвертированию форматов файла-источника.
* Форматы файлов указываются в нижнем регистре (пример: 'jpg')
* @var string[]
*/
protected const VALID_SOURCE_FILE_FORMATS = array(
'jpg',
'jpeg',
'bmp',
'png'
);
/**
* Выполняет конвертирование файла-источника в выходной файл
* @param string $pathFileFrom - путь к файлу-источнику
* @param string $pathFileTo - путь к выходному файлу
* @return string[]
*/
public function convert($pathFileFrom, $pathFileTo)
{
$return = array(
'status' => '',
'statusDesc' => ''
);
if ($this->sourceFileIsValid($pathFileFrom) == false) {
$return['status'] = 'error';
$return['statusDesc'] = 'Недопустимый формат файла-источника картинки. Допускаются следующие форматы файлов: '.implode(', ', $this::VALID_SOURCE_FILE_FORMATS);
} else {
$result = $this->mediaContainer->convert($pathFileFrom, $pathFileTo);
if ($result == true) {
$return['status'] = 'success';
$return['statusDesc'] = $result['statusDesc'];
} else {
$return['status'] = 'error';
$return['statusDesc'] = 'Не удалось выполнить конвертирование файла';
}
}
return $return;
}
}
/**
* Конвертер музыкальных файлов
*/
class ConverterAudio extends Converter
{
/**
* Массив разрешённых к конвертированию форматов файла-источника.
* Форматы файлов указываются в нижнем регистре (пример: 'mp3')
* @var string[]
*/
protected const VALID_SOURCE_FILE_FORMATS = array(
'mp3',
'wma',
'ogg'
);
/**
* Выполняет конвертирование файла-источника в выходной файл
* @param string $pathFileFrom - путь к файлу-источнику
* @param string $pathFileTo - путь к выходному файлу
* @return string[]
*/
public function convert($pathFileFrom, $pathFileTo)
{
$return = array(
'status' => '',
'statusDesc' => ''
);
if ($this->sourceFileIsValid($pathFileFrom) == false) {
$return['status'] = 'error';
$return['statusDesc'] = 'Недопустимый формат музыкального файла-источника. Допускаются следующие форматы файлов: '.implode(', ', $this::VALID_SOURCE_FILE_FORMATS);
} else {
$result = $this->mediaContainer->convert($pathFileFrom, $pathFileTo);
if ($result == true) {
$return['status'] = 'success';
$return['statusDesc'] = $result['statusDesc'];
} else {
$return['status'] = 'error';
$return['statusDesc'] = 'Не удалось выполнить конвертирование файла';
}
}
return $return;
}
}
?>
media_containers.classes.php
Содержит классы медиаконтейнеров. Медиаконтейнер отвечает за формат выходного файла и именно он делает непосредственную конвертацию. В нашем примере представлены следующие контейнеры:- MediaContainer — абстрактный класс медиаконтейнера. Содержит абстрактный метод convert($pathFileFrom, $pathFileTo), с помощью которого выполняется конвертация.
- MediaContainerJPG — класс-медиаконтейнер определённым методом convert для конвертации картинки в формат JPG.
- MediaContainerPNG — класс-медиаконтейнер с определённым методом convert для конвертации картинки в формат PNG.
- MediaContainerMP3 — класс-медиаконтейнер с определённым методом convert для конвертации аудиофайла в формат MP3.
- MediaContainerOGG — класс-медиаконтейнер с определённым методом convert для конвертации аудиофайла в формат OGG.
<?
/**
* Абстрактный класс медиаконтейнера
*/
abstract class MediaContainer
{
/**
* Выполняет конвертирование файла-источника в выходной файл
* @param string $pathFileFrom - путь к файлу-источнику
* @param string $pathFileTo - путь к выходному файлу
*
* @return string[] - вернёт массив со сведениями о результатах конвертации.
* В случае успеха элемент-ключ 'status' будет содержать значение 'success',
* а в случае ошибки будет содержать значение 'error' и в элементе-ключе 'statusDesc'
* будет содержаться описание ошибки.
*/
abstract public function convert($pathFileFrom, $pathFileTo);
}
/**
* Контейнер картинок формата JPG
*/
class MediaContainerJPG extends MediaContainer
{
public function convert($pathFileFrom, $pathFileTo)
{
$return = array(
'status' => '',
'statusDesc' => ''
);
//тут располагается код, выполняющий непосредственное конвертирование файла в JPG
$return['status'] = 'success';
$return['statusDesc'] = 'Файл '.$pathFileFrom.' конвертирован в JPG и сохранён по пути '.$pathFileTo;
return $return;
}
}
/**
* Контейнер картинок формата PNG
*/
class MediaContainerPNG extends MediaContainer
{
public function convert($pathFileFrom, $pathFileTo)
{
$return = array(
'status' => '',
'statusDesc' => ''
);
//тут располагается код, выполняющий непосредственное конвертирование файла в PNG
$return['status'] = 'success';
$return['statusDesc'] = 'Файл '.$pathFileFrom.' конвертирован в PNG и сохранён по пути '.$pathFileTo;
return $return;
}
}
/**
* Контейнер музыкальных файлов формата MP3
*/
class MediaContainerMP3 extends MediaContainer
{
public function convert($pathFileFrom, $pathFileTo)
{
$return = array(
'status' => '',
'statusDesc' => ''
);
//тут располагается код, выполняющий непосредственное конвертирование файла в MP3
$return['status'] = 'success';
$return['statusDesc'] = 'Файл '.$pathFileFrom.' конвертирован в MP3 и сохранён по пути '.$pathFileTo;
return $return;
}
}
/**
* Контейнер музыкальных файлов формата OGG
*/
class MediaContainerOGG extends MediaContainer
{
public function convert($pathFileFrom, $pathFileTo)
{
$return = array(
'status' => '',
'statusDesc' => ''
);
//тут располагается код, выполняющий непосредственное конвертирование файла в OGG
$return['status'] = 'success';
$return['statusDesc'] = 'Файл '.$pathFileFrom.' конвертирован в OGG и сохранён по пути '.$pathFileTo;
return $return;
}
}
?>
test.php
Скрипт с клиентским кодом, в котором создаются объекты конвертеров и выполняется конвертация файлов. При попытке конвертирования файла mp4 в jpg произойдёт ошибка, ведь данные форматы не являются родственными.Обратите внимание, что клиентский код работает исключительно с классами конвертеров (сущностями) и не касается классов медиаконтейнеров (функциональностей), хотя непосредственное конвертирование выполняется в медиаконтейнерах.
<?
//Подключение классов конвертеров
require_once(__DIR__.'/converters.classes.php');
//Создаём JPG-конвертер
$jpgConverter = new ConverterImage('JPG');
/*
Тут должна возникнуть ошибка конвертирования, т.к в источнике указан файл mp4,
а такой формат недопустим для конвертации в jpg
*/
$result = $jpgConverter->convert('/images_from/1.mp4', '/images_to/1.jpg');
echo '<pre>'.print_r($result, true).'</pre>';
//Конвертируем файл в JPG
$result = $jpgConverter->convert('/images_from/1.png', '/images_to/1.jpg');
echo '<pre>'.print_r($result, true).'</pre>';
//Создаём PNG-конвертер и конвертируем файл
$pngConverter = new ConverterImage('PNG');
$result = $pngConverter->convert('/images_from/2.bmp', '/images_to/2.png');
echo '<pre>'.print_r($result, true).'</pre>';
//Создаём MP3-конвертер и конвертируем файл
$mp3Converter = new ConverterAudio('MP3');
$result = $mp3Converter->convert('/music_from/1.ogg', '/music_to/1.mp3');
echo '<pre>'.print_r($result, true).'</pre>';
//Создаём OGG-конвертер и конвертируем файл
$oggConverter = new ConverterAudio('OGG');
$result = $oggConverter->convert('/music_from/1.wma', '/music_to/1.ogg');
echo '<pre>'.print_r($result, true).'</pre>';
?>
Результат работы скрипта test.php
Array
(
[status] => error
[statusDesc] => Недопустимый формат файла-источника картинки. Допускаются следующие форматы файлов: jpg, jpeg, bmp, png
)
Array
(
[status] => success
[statusDesc] => Файл /images_from/1.png конвертирован в JPG и сохранён по пути /images_to/1.jpg
)
Array
(
[status] => success
[statusDesc] => Файл /images_from/2.bmp конвертирован в PNG и сохранён по пути /images_to/2.png
)
Array
(
[status] => success
[statusDesc] => Файл /music_from/1.ogg конвертирован в MP3 и сохранён по пути /music_to/1.mp3
)
Array
(
[status] => success
[statusDesc] => Файл /music_from/1.wma конвертирован в OGG и сохранён по пути /music_to/1.ogg
)