Мост — это структурный паттерн, который разделяет бизнес-логику или большой класс на несколько отдельных иерархий, которые потом можно развивать отдельно друг от друга.
Одна из этих иерархий (абстракция) получит ссылку на объекты другой иерархии (реализация) и будет делегировать им основную работу. Благодаря тому, что все реализации будут следовать общему интерфейсу, их можно будет взаимозаменять внутри абстракции.
Концептуальный пример
Этот пример показывает структуру паттерна Мост , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php
namespace RefactoringGuru\Bridge\Conceptual;
/**
* Абстракция устанавливает интерфейс для «управляющей» части двух иерархий
* классов. Она содержит ссылку на объект из иерархии Реализации и делегирует
* ему всю настоящую работу.
*/
class Abstraction
{
/**
* @var Implementation
*/
protected $implementation;
public function __construct(Implementation $implementation)
{
$this->implementation = $implementation;
}
public function operation(): string
{
return "Abstraction: Base operation with:\n" .
$this->implementation->operationImplementation();
}
}
/**
* Можно расширить Абстракцию без изменения классов Реализации.
*/
class ExtendedAbstraction extends Abstraction
{
public function operation(): string
{
return "ExtendedAbstraction: Extended operation with:\n" .
$this->implementation->operationImplementation();
}
}
/**
* Реализация устанавливает интерфейс для всех классов реализации. Он не должен
* соответствовать интерфейсу Абстракции. На практике оба интерфейса могут быть
* совершенно разными. Как правило, интерфейс Реализации предоставляет только
* примитивные операции, в то время как Абстракция определяет операции более
* высокого уровня, основанные на этих примитивах.
*/
interface Implementation
{
public function operationImplementation(): string;
}
/**
* Каждая Конкретная Реализация соответствует определённой платформе и реализует
* интерфейс Реализации с использованием API этой платформы.
*/
class ConcreteImplementationA implements Implementation
{
public function operationImplementation(): string
{
return "ConcreteImplementationA: Here's the result on the platform A.\n";
}
}
class ConcreteImplementationB implements Implementation
{
public function operationImplementation(): string
{
return "ConcreteImplementationB: Here's the result on the platform B.\n";
}
}
/**
* За исключением этапа инициализации, когда объект Абстракции связывается с
* определённым объектом Реализации, клиентский код должен зависеть только от
* класса Абстракции. Таким образом, клиентский код может поддерживать любую
* комбинацию абстракции и реализации.
*/
function clientCode(Abstraction $abstraction)
{
// ...
echo $abstraction->operation();
// ...
}
/**
* Клиентский код должен работать с любой предварительно сконфигурированной
* комбинацией абстракции и реализации.
*/
$implementation = new ConcreteImplementationA();
$abstraction = new Abstraction($implementation);
clientCode($abstraction);
echo "\n";
$implementation = new ConcreteImplementationB();
$abstraction = new ExtendedAbstraction($implementation);
clientCode($abstraction);
Output.txt: Результат выполнения
Abstraction: Base operation with:
ConcreteImplementationA: Here's the result on the platform A.
ExtendedAbstraction: Extended operation with:
ConcreteImplementationB: Here's the result on the platform B.
Пример из реальной жизни
В этом примере иерархия Страницы выступает как Абстракция, а иерархия Рендера как Реализация. Объекты класса Страница монтируют веб-страницы определённого типа, используя базовые элементы, которые предоставляются объектом Рендер, прикреплённым к этой странице. Обе иерархии классов разделены, поэтому можно добавить новый класс Рендер без изменения классов страниц и наоборот.
index.php: Пример из реальной жизни
<?php
namespace RefactoringGuru\Bridge\RealWorld;
/**
* Абстракция.
*/
abstract class Page
{
/**
* @var Renderer
*/
protected $renderer;
/**
* Обычно Абстракция инициализируется одним из объектов Реализации.
*/
public function __construct(Renderer $renderer)
{
$this->renderer = $renderer;
}
/**
* Паттерн Мост позволяет динамически заменять присоединённый объект
* Реализации.
*/
public function changeRenderer(Renderer $renderer): void
{
$this->renderer = $renderer;
}
/**
* Поведение «вида» остаётся абстрактным, так как оно предоставляется только
* классами Конкретной Абстракции.
*/
abstract public function view(): string;
}
/**
* Эта Конкретная Абстракция создаёт простую страницу.
*/
class SimplePage extends Page
{
protected $title;
protected $content;
public function __construct(Renderer $renderer, string $title, string $content)
{
parent::__construct($renderer);
$this->title = $title;
$this->content = $content;
}
public function view(): string
{
return $this->renderer->renderParts([
$this->renderer->renderHeader(),
$this->renderer->renderTitle($this->title),
$this->renderer->renderTextBlock($this->content),
$this->renderer->renderFooter()
]);
}
}
/**
* Эта Конкретная Абстракция создаёт более сложную страницу.
*/
class ProductPage extends Page
{
protected $product;
public function __construct(Renderer $renderer, Product $product)
{
parent::__construct($renderer);
$this->product = $product;
}
public function view(): string
{
return $this->renderer->renderParts([
$this->renderer->renderHeader(),
$this->renderer->renderTitle($this->product->getTitle()),
$this->renderer->renderTextBlock($this->product->getDescription()),
$this->renderer->renderImage($this->product->getImage()),
$this->renderer->renderTextBlock('$'.number_format($this->product->getPrice(), 2)),
$this->renderer->renderLink("/cart/add/".$this->product->getId(), "Add to cart"),
$this->renderer->renderFooter()
]);
}
}
/**
* Вспомогательный класс для класса ProductPage.
*/
class Product
{
private $id, $title, $description, $image, $price;
public function __construct(
string $id,
string $title,
string $description,
string $image,
float $price
) {
$this->id = $id;
$this->title = $title;
$this->description = $description;
$this->image = $image;
$this->price = $price;
}
public function getId(): string { return $this->id; }
public function getTitle(): string { return $this->title; }
public function getDescription(): string { return $this->description; }
public function getImage(): string { return $this->image; }
public function getPrice(): float { return $this->price; }
}
/**
* Реализация объявляет набор «реальных», «под капотом», «платформенных»
* методов.
*
* В этом случае Реализация перечисляет методы рендеринга, которые используются
* для создания веб-страниц. Разные Абстракции могут использовать разные методы
* Реализации.
*/
interface Renderer
{
public function renderTitle(string $title): string;
public function renderTextBlock(string $text): string;
public function renderImage(string $url): string;
public function renderLink(string $url, string $title): string;
public function renderHeader(): string;
public function renderFooter(): string;
public function renderParts(array $parts): string;
}
/**
* Эта Конкретная Реализация отображает веб-страницу в виде HTML.
*/
class HTMLRenderer implements Renderer
{
public function renderTitle(string $title): string
{
return "<h1>$title</h1>";
}
public function renderTextBlock(string $text): string
{
return "<div class='text'>$text</div>";
}
public function renderImage(string $url): string
{
return "<img src='$url'>";
}
public function renderLink(string $url, string $title): string
{
return "<a href='$url'>$title</a>";
}
public function renderHeader(): string
{
return "<html><body>";
}
public function renderFooter(): string
{
return "</body></html>";
}
public function renderParts(array $parts): string
{
return implode("\n", $parts);
}
}
/**
* Эта Конкретная Реализация отображает веб-страницу в виде строк JSON.
*/
class JsonRenderer implements Renderer
{
public function renderTitle(string $title): string
{
return '"title": "' . $title . '"';
}
public function renderTextBlock(string $text): string
{
return '"text": "' . $text . '"';
}
public function renderImage(string $url): string
{
return '"img": "' . $url . '"';
}
public function renderLink(string $url, string $title): string
{
return '"link": {"href": "' . $url . '", "title": "' . $title . '"}';
}
public function renderHeader(): string
{
return '';
}
public function renderFooter(): string
{
return '';
}
public function renderParts(array $parts): string
{
return "{\n" . implode(",\n", array_filter($parts)) . "\n}";
}
}
/**
* Клиентский код имеет дело только с объектами Абстракции.
*/
function clientCode(Page $page)
{
// ...
echo $page->view();
// ...
}
/**
* Клиентский код может выполняться с любой предварительно сконфигурированной
* комбинацией Абстракция+Реализация.
*/
$HTMLRenderer = new HTMLRenderer();
$JSONRenderer = new JsonRenderer();
$page = new SimplePage($HTMLRenderer, "Home", "Welcome to our website!");
echo "HTML view of a simple content page:\n";
clientCode($page);
echo "\n\n";
/**
* При необходимости Абстракция может заменить связанную Реализацию во время
* выполнения.
*/
$page->changeRenderer($JSONRenderer);
echo "JSON view of a simple content page, rendered with the same client code:\n";
clientCode($page);
echo "\n\n";
$product = new Product("123", "Star Wars, episode1",
"A long time ago in a galaxy far, far away...",
"/images/star-wars.jpeg", 39.95);
$page = new ProductPage($HTMLRenderer, $product);
echo "HTML view of a product page, same client code:\n";
clientCode($page);
echo "\n\n";
$page->changeRenderer($JSONRenderer);
echo "JSON view of a simple content page, with the same client code:\n";
clientCode($page);
Output.txt: Результат выполнения
HTML view of a simple content page:
<html><body>
<h1>Home</h1>
<div class='text'>Welcome to our website!</div>
</body></html>
JSON view of a simple content page, rendered with the same client code:
{
"title": "Home",
"text": "Welcome to our website!"
}
HTML view of a product page, same client code:
<html><body>
<h1>Star Wars, episode1</h1>
<div class='text'>A long time ago in a galaxy far, far away...</div>
<img src='/images/star-wars.jpeg'>
<a href='/cart/add/123'>Add to cart</a>
</body></html>
JSON view of a simple content page, with the same client code:
{
"title": "Star Wars, episode1",
"text": "A long time ago in a galaxy far, far away...",
"img": "/images/star-wars.jpeg",
"link": {"href": "/cart/add/123", "title": "Add to cart"}
}