Рассматривается структурный шаблон проектирования Bridge (Мост), его применение, приведены структура шаблона и код его реализации.
Содержание
- Применение
- Описание шаблона
- Структура шаблона
- Реализация
- Итоги
Применение
Формальное определение шаблона проектирования Bridge гласит: это структурный шаблон проектирования программного обеспечения, используемый для разделения абстракции и реализации с целью их независимого изменения. Т.е., один программист решает, как должен будет вести себя компонент, а второй реализует это в коде, причем создаваемый ими код не зависит от другого. А ведь это — возможность параллельной работы — мечта любого менеджера. Думаю, идея шаблона понятна, но не хватает конкретики. Вот этим и займёмся.
Описание шаблона
Представьте, что нам предстоит создать достаточно непростое ПО, где будет необходим компонент, управляющий, например, средствами передвижения. Будет логичным выделить его в отдельный класс. Создадим абстракцию автомашин Car, а конкретное исполнение будем реализовывать в наследниках, описывающих конкретные средства передвижения примерно так.
Часть кода описывающая средства передвижения в проекте может быть достаточно объемной — это классы, иx наследники, объекты, их взаимодействие и прочее. И вот, как обычно бывае, в исходный код Car потребовали внести изменения. В итоге вам придется править уже созданный и оттестированный код по всему проекту, что может повлечь за собой ошибки, новое тестирование и прочие неприятные мероприятия. Отсюда вывод — наследование вещь весьма полезная в ООП, но не в этом случае.
Минимизировать подобные последствия помогает Bridge. Шаблон позволяет разорвать жесткую зависимость классов друг от друга. За счет чего это происходит?
При использовании шаблона Bridge вместо классической схемы построения иерархии классов с наследованием мы создадим две независимые иерархии. Одна будет описывать один слой управления средствами передвижения, например, более высокого(общего) уровня приложения. Этот слой в терминах шаблона называется абстракция. Вторая иерархия будет описывать слой управления более низкого уровня, ориентированного на конкретное исполнение средств передвижения. Этот слой в терминах шаблона называется реализация. Связь между ними (мост) будет осуществляться с помощью ссылки на объект реализации, которая передается в объект абстракции средств передвижения. Прошу не путать упомянутые «абстракция» и «реализация» с аналогичными терминами в ООП, в данном случае это термины шаблона. Теперь диаграмма будет выглядеть так.
Пунктирная линия со стрелкой отображает связь между созданными иерархиями.
Структура шаблона
Диаграммы хороши, когда надо отобразить общую картину, но всех деталей не показывает. Поэтому продолжим на конкретном примере. В нём участвуют.
- Car — абстрактный класс, описывает поведение и свойства автомобилей.
- FordCar — класс, описывающий автомобили бренда Ford, реализует Car.
- RenoCar — класс, описывающий автомобили бренда Reno, реализует Car.
- BmvCar — класс, описывающий автомобили бренда Bmv, реализует Car.
- interface Brand — интерфейс, декларирует поведение классов конкретного бренда автомобилей.
- FordBrand — класс, описывающий автомобили Ford, реализует интерфейс Brand.
- RenoBrand — класс, описывающий автомобили Reno, реализует интерфейс Brand.
- BmvBrand — класс, описывающий автомобили Bmv, реализует интерфейс Brand.
Пусть вас не смущает длинный список участников, все они просты и однотипны. Замечу, что по участникам можно увидеть две иерархии классов. Может показаться, что происходит дублирование поведения в интерфейсах. Но это не так, потому что интерфейс абстракции описывает поведение классов на более высоком уровне приложения, а интерфейс реализации призван объявить поведение на более низком(детальном) уровне. Например, абстракция описывает программный компонент в общем, а реализация обеспечивает её детализацию для конкретного исполнения. Повторюсь, связь между ними осуществляется за счет ссылки в объекте абстракции на объект реализации. Эта ссылка является мостом, который связывает обе иерархии. В итоге в приложении объект абстракции определяет поведение и свойства в общем виде, а детализацией занимается объект реализации. Кроме того, с использованием шаблона Bridge приложение может динамически комбинировать созданием компонентов, передавая объекту абстракции ссылки на различные объекты реализации.
Итак, подытожим.
Классы каждой иерархии имеют свои интерфейсы. Второе, объект абстракции должен хранить ссылку на интерфейс реализации. Детали ищите в коде примера.
Реализация
Маленькая преамбула. Код примера не претендует на совершенство и изящество, поскольку стояла задача как можно проще донести идею шаблона Bridge.
Ниже приведен код абстракции.
<?php
declare(strict_types=1);
abstract class Car
{
protected Brand $brand;
public function __construct(Brand $brand)
{
$this->brand = $brand;
}
abstract public function getModelName(string $name): string;
abstract public function getAvatarPath(string $model): string;
abstract public function refuel(string $name, string $level): string;
}
class FordCar extends Car
{
protected array $models = [
'fiesta',
'focus',
'fusion',
'puma gen-e',
];
public function getModelName(string $name): string
{
$key = array_search(
mb_strtolower(trim($name), 'UTF-8'),
$this->models,
true
);
if ($key === false) {
return '';
}
return $this->models[$key];
}
public function getAvatarPath(string $model): string
{
if ($model !== '') {
return __DIR__ . '/images/cars/' . $model . 'Avatar.png';
}
return __DIR__ . '/images/cars/nonameAvatar.png';
}
public function refuel(string $name, string $level): string
{
return $this->brand->setEnergy(string $model, string $level);
}
}
class RenoCar implements Car
{
protected array $models = [
'clio',
'captur',
'megane',
'duster',
];
public function getModelName(string $name): string
{
$key = array_search(
mb_strtolower(trim($name), 'UTF-8'),
$this->models,
true
);
if ($key === false) {
return '';
}
return $this->models[$key];
}
public function getAvatarPath(string $model): string
{
if ($model !== '') {
return __DIR__ . '/images/cars/' . $model . 'Avatar.png';
}
return __DIR__ . '/images/cars/nonameAvatar.png';
}
public function refuel(string $model, string $level): string
{
return $this->brand->setEnergy(string $model, string $level);
}
}
class BmvCar implements Car
{
protected array $models = [
'БМВ Х3',
'BMW 650i',
'BMW M8',
];
public function getModelName(string $name): string
{
$key = array_search(
mb_strtolower(trim($name), 'UTF-8'),
$this->models,
true
);
if ($key === false) {
return '';
}
return $this->models[$key];
}
public function getAvatarPath(string $model): string
{
if ($model !== '') {
return __DIR__ . '/images/cars/' . $model . 'Avatar.png';
}
return __DIR__ . '/images/cars/nonameAvatar.png';
}
public function refuel(string $model, string $level): string
{
return $this->brand->setEnergy(string $model, string $level);
}
}
А вот код реализации. Обратите внимание, что модель бренда Ford осуществляет анализ модели и лишь потом выбирает вид заправки.
interface Brand
{
public function setEnergy(string $model, string $level): string;
}
class FordBrand implements Brand
{
public function setEnergy(string $model, string $level): string
{
if ($model === 'puma gen-e') {
return 'Произведена зарядка батарей автомобиля ' .$model .' до уровня ' . $level';
}
return 'Произведена заправка автомобиля ' .$model .' до уровня ' . $level';
}
}
class RenoBrand implements Brand
{
public function setEnergy(string $model, int $level): string
{
return 'Произведена заправка автомобиля ' .$model .' до уровня ' . $level';
}
}
class BmvBrand implements Brand
{
public function setEnergy(string $model, int $level): string
{
return 'Произведена заправка автомобиля ' .$model .' до уровня ' . $level';
}
}
И последнее — как это все работает. Приложение определяет требуемую ему модель, получает его аватар. А вот произвести заправку автомобиля он делегирует встроенному классу, который на более низком уровне обеспечивает полное управление(в примере для простоты приведено не всё): проверит тип двигателя, требуемое топливо или заряд батареи и т.п.
$brand = new FordBrand();
$car = new FordCar($brand);
$model = $car->getModelName('Focus');
$avatar = $car->getAvatarPath('focus');
$list = $car->refuel($model, '50%');
...
Итоги
Что мы теперь имеем в итоге?
При использовании шаблона проектирования Bridge вместо использования наследования, в котором классы жестко связаны, мы убрали эту зависимость, разделив классы с помощью моста на две независимые иерархии. Что это дало?
На стадии проектирования мы можем параллельно создавать независимые иерархии классов. Здесь ключевое слово «параллельно», т.к. Bridge позволяет разделить разработку на автономные ветки.
Основные преимущества шаблона.
- Позволяет вести параллельную разработку проекта.
- Позволяет вести проект, который просто адаптируется под конкретные условия.
- Клиентский не обязан знать детали реализации, поскольку их можно поместить в слой реализации.
Недостатки.
- Усложнение кода из-за введения дополнительных классов.
- Возможно усложнение отладки кода.
На этом пока всё.
Успехов в разработке!
Перейти к списку шаблонов проектирования.

