Le Pont est un patron de conception structurel qui scinde la logique métier ou divise de grandes classes dans des hiérarchies de classes séparées qui vont ensuite évoluer indépendamment.
Une de ces hiérarchies (souvent appelée l’abstraction) gardera une référence vers un objet de la seconde hiérarchie (l’implémentation). L’abstraction pourra déléguer certains (parfois la majorité) de ses appels aux objets de l’implémentation. Puisque toutes les implémentations ont une interface commune, elles sont interchangeables à l’intérieur de l’abstraction.
Exemple conceptuel
Dans cet exemple, nous allons voir la structure du Pont et répondre aux questions suivantes :
Que contiennent les classes ?
Quels rôles jouent-elles ?
Comment les éléments du patron sont-ils reliés ?
Après avoir étudié la structure du patron, vous pourrez plus facilement comprendre l’exemple suivant qui est basé sur un cas d’utilisation réel en PHP.
index.php: Exemple conceptuel
<?php
namespace RefactoringGuru\Bridge\Conceptual;
/**
* The Abstraction defines the interface for the "control" part of the two class
* hierarchies. It maintains a reference to an object of the Implementation
* hierarchy and delegates all of the real work to this object.
*/
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();
}
}
/**
* You can extend the Abstraction without changing the Implementation classes.
*/
class ExtendedAbstraction extends Abstraction
{
public function operation(): string
{
return "ExtendedAbstraction: Extended operation with:\n" .
$this->implementation->operationImplementation();
}
}
/**
* The Implementation defines the interface for all implementation classes. It
* doesn't have to match the Abstraction's interface. In fact, the two
* interfaces can be entirely different. Typically the Implementation interface
* provides only primitive operations, while the Abstraction defines higher-
* level operations based on those primitives.
*/
interface Implementation
{
public function operationImplementation(): string;
}
/**
* Each Concrete Implementation corresponds to a specific platform and
* implements the Implementation interface using that platform's 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";
}
}
/**
* Except for the initialization phase, where an Abstraction object gets linked
* with a specific Implementation object, the client code should only depend on
* the Abstraction class. This way the client code can support any abstraction-
* implementation combination.
*/
function clientCode(Abstraction $abstraction)
{
// ...
echo $abstraction->operation();
// ...
}
/**
* The client code should be able to work with any pre-configured abstraction-
* implementation combination.
*/
$implementation = new ConcreteImplementationA();
$abstraction = new Abstraction($implementation);
clientCode($abstraction);
echo "\n";
$implementation = new ConcreteImplementationB();
$abstraction = new ExtendedAbstraction($implementation);
clientCode($abstraction);
Output.txt: Résultat de l’exécution
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.
Analogie du monde réel
Dans cet exemple, la hiérarchie de la Page
joue le rôle de l’Abstraction et la hiérarchie du Convertisseur
(renderer), celui de l’Implémentation . Les objets de la classe Page
peuvent assembler des pages Web d’un type particulier en utilisant des éléments basiques que l’objet Convertisseur
affecté à la page leur a fournis. Puisque les deux hiérarchies de classes sont séparées, vous pouvez ajouter une nouvelle classe Convertisseur
sans toucher aux classes Page
et inversement.
index.php: Exemple du monde réel
<?php
namespace RefactoringGuru\Bridge\RealWorld;
/**
* The Abstraction.
*/
abstract class Page
{
/**
* @var Renderer
*/
protected $renderer;
/**
* The Abstraction is usually initialized with one of the Implementation
* objects.
*/
public function __construct(Renderer $renderer)
{
$this->renderer = $renderer;
}
/**
* The Bridge pattern allows replacing the attached Implementation object
* dynamically.
*/
public function changeRenderer(Renderer $renderer): void
{
$this->renderer = $renderer;
}
/**
* The "view" behavior stays abstract since it can only be provided by
* Concrete Abstraction classes.
*/
abstract public function view(): string;
}
/**
* This Concrete Abstraction represents a simple page.
*/
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()
]);
}
}
/**
* This Concrete Abstraction represents a more complex page.
*/
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()
]);
}
}
/**
* A helper class for the ProductPage class.
*/
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; }
}
/**
* The Implementation declares a set of "real", "under-the-hood", "platform"
* methods.
*
* In this case, the Implementation lists rendering methods that can be used to
* compose any web page. Different Abstractions may use different methods of the
* Implementation.
*/
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;
}
/**
* This Concrete Implementation renders a web page as 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);
}
}
/**
* This Concrete Implementation renders a web page as JSON strings.
*/
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}";
}
}
/**
* The client code usually deals only with the Abstraction objects.
*/
function clientCode(Page $page)
{
// ...
echo $page->view();
// ...
}
/**
* The client code can be executed with any pre-configured combination of the
* Abstraction+Implementation.
*/
$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";
/**
* The Abstraction can change the linked Implementation at runtime if needed.
*/
$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: Résultat de l’exécution
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"}
}