Accueil / Tutoriels / Bridge

Bridge

Publié le 23 Mai 2025
Conception

Bridge est un design pattern structurel qui vise à découpler une abstraction de son implémentation de manière à ce qu’elles puissent évoluer indépendamment. C’est un pattern très utile lorsqu’on anticipe que plusieurs instances déclinaisons de classes peuvent exister.

Principe

Le but est d’éviter une explosion de classes quand on a plusieurs critères de variation. On peut prendre pour exemple des véhicules. On a trois types de véhicules (voiture, camion, moto) et trois types de moteurs (diesel, essence, électrique). Si on veut créer une classe pour chaque combinaison possible, on va devoir ajouter pour chaque véhicule un nouveau type de moteur et inversement.

Avec cette logique, le code va vite devenir difficile à maintenir. Le principe de responsabilité unique n’est plus respecté car chaque classe doit gérer la logique du véhicule et du moteur. Il y a une forte dépendance entre les véhicules et les moteurs.

L’idée du pattern Bridge va être de séparer l’abstraction (le véhicule) de l'implémentation (moteur). On obtient ainsi une architecture modulaire. Si on veut ajouter un moteur hybride à notre liste, cela peut se faire facilement sans avoir à toucher à nos véhicules. Inversement, on peut ajouter un nouveau véhicule sans avoir à toucher à la logique des moteurs.

Exemples d’utilisations

Un système d’authentifications multiples

Sur une application web, on propose une connexion classique (email et mot de passe) et une connexion (OAuth) via Google, Apple ou Facebook. On souhaite garder une interface utilisateur homogène avec la même page de connexion et la même logique de connexion.

Avec le pattern Bridge on va créer une abstraction (exemple AuthService) qui exposera des méthode login() et logout(). Cette abstraction sera utilisée par les implémentations comme FormAuth (pour le login et mot de passe) ou OAuthAuth (pour Google, Apple, etc…). Chacune de ces implémentations met en place sa logique pour l’authentification.

Générateur de contenu multi format

Un outil de génération de contenus nous propose différents formats de sorties (HTML, PDF, Markdown, etc…). Le contenu reste le même, seul le format change.

Le pattern Bridge va permettre de séparer l’abstraction (le contenu) de l’implémentation (le format) avec par exemple un HTMLRenderer, PDFRenderer ou MarkdownRenderer.

Points d’attentions

Le pattern Bridge introduit plus de classes, plus d’abstractions et souvent plus d’interfaces que nécessaire si le code en place ne justifie pas ces séparations. Pour un système simple, cette séparation est inutile. On perd en lisibilité et on complexifie inutilement le code.

Comme l’abstraction et l’implémentation sont découplées, il faut les lier manuellement (injection de dépendance, factory, config, etc…). La configuration devient plus verbeuse et plus fragile.

Implémenter un Bridge

L’objectif va être de séparer l’abstraction de l’implémentation réelle pour pouvoir modifier l’un sans avoir à toucher à l’autre.

On commence par créer l’abstraction principale. Elle définit l’interface logique à utiliser. Nous allons ensuite créer l’interface d’implémentation (le Bridge) qui correspond à l’interface que notre abstraction principale utilisera. Ensuite, on implémente les différentes classes concrètes qui contiennent leurs logiques propres en fonction de leurs rôles respectifs.

Voici un exemple en Go

Sur notre application, nous avons un système de log sur lequel on veut changer le format de log (JSON, XML, texte) et changer la sortie (fichier, stdout, syslog).

Ces deux dimensions (format et sortie) peuvent évoluer indépendamment. Voilà un cas d’utilisation pertinent pour le pattern Bridge.


package logs

import "fmt"

// LogOutput Implémentation (le backend)
type LogOutput interface {
   Write(message string)
}

type FileOutput struct{}

func (f *FileOutput) Write(message string) {
   fmt.Println("File:", message) // ici on simule
}

type StdoutOutput struct{}

func (s *StdoutOutput) Write(message string) {
   fmt.Println("Stdout:", message)
}

// Logger Abstraction
type Logger struct {
   output LogOutput
   format string
}

func NewLogger(output LogOutput, format string) *Logger {
   return &Logger{output: output, format: format}
}

func (l *Logger) Log(message string) {
   var formatted string

   switch l.format {
   case "json":
      formatted = fmt.Sprintf(`{"message": "%s"}`, message)
   default:
      formatted = message
   }

   l.output.Write(formatted)
}


package main

import "designpatterns/bridge/logs"

func main() {
   stdout := &logs.StdoutOutput{}
   logger := logs.NewLogger(stdout, "json")
   logger.Log("User logged in")
}

Le même exemple en PHP


namespace Practice\DesignPatterns\Bridge;

interface LogOutputInterface {
   public function write(string $message): void;
}


namespace Practice\DesignPatterns\Bridge;

class FileOutput implements LogOutputInterface {
   public function write(string $message): void {
       echo "File: $message\n"; // ici on simule
   }
}


namespace Practice\DesignPatterns\Bridge;

class StdoutOutput implements LogOutputInterface {
   public function write(string $message): void {
       echo "Stdout: $message\n";
   }
}


namespace Practice\DesignPatterns\Bridge;

class Logger {
   private LogOutputInterface $output;
   private string $format;

   public function __construct(LogOutputInterface $output, string $format) {
       $this->output = $output;
       $this->format = $format;
   }

   public function log(string $message): void {
       switch ($this->format) {
           case 'json':
               $formatted = json_encode(['message' => $message]);
               break;
           default:
               $formatted = $message;
       }
       $this->output->write($formatted);
   }
}


declare(strict_types=1);

require "./vendor/autoload.php";

use Practice\DesignPatterns\Bridge\Logger;
use Practice\DesignPatterns\Bridge\StdoutOutput;

$output = new StdoutOutput();
$logger = new Logger($output, 'json');
$logger->log("User logged in");

Conclusion

J'espère que grâce à cet article vous êtes désormais plus à l'aise avec le design pattern Bridge. Vous pouvez retrouver mon article sur les patrons de conception ou les autres tutoriels sur Observer, Strategy, Memento, Facade et Factory.