Bridge

La programmation avec design patterns

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Les ponts n'existent pas que dans le domaine des congés. Ils ont trouvé leur emplacement aussi dans le monde informatique, et plus particulièrement dans celui des design patterns. Le patron dont va traiter cet article s'appelle Bridge (pont en français).

Bridge appartient à la famille des design patterns structurels. On peut donc dire tout de suite qu'il définit la composition des objets pour obtenir une nouvelle fonctionnalité. Ce patron de conception se base sur la séparation de l'abstraction d'un concept de l'implémentation de ce concept. Cela provoque la création des 2 couches : haut niveau et bas niveau. Les deux couches sont indépendantes et peuvent être manipulées librement.

Quatre acteurs sont présents dans ce design pattern. Pour bien le comprendre, on les divise en 2 groupes : d'abstraction et d'implémentation. Dans le groupe d'abstraction on retrouve une Abstraction. Elle est une classe abstraite qui définit le comportement à adopter par RefinedAbstraction pour communiquer avec le groupe d'implémentation. Toutes les classes du groupe d'abstraction contiennent une référence vers le groupe d'implémentation. En ce qui concerne ce dernier, une interface nommée Implementator y est présente. Les définition de ses méthodes précisent quelles actions peuvent être lancées par l'Abstraction. Les autres éléments de ce groupe sont des implémentations concrètes de l'interface. On peut les appeler ConcreteImplementators.

D'après ce bref récit, on déduit que dans Bridge l'application client communique uniquement avec l'implémentation de l'Abstraction. Ensuite c'est elle qui s'occupe d'interagir avec des ConcreteImplementators adéquats. Toute cette communication s'effectue grâce à l'héritage, l'encapsulation et l'aggrégation (relations entre des classes).

Si l'on recherche l'analogie dans le monde réel, on peut immédiatement penser à la télévision et sa télécommande qui, grâce au client (téléspectateur), influence le comportement du poste. Une autre illustration est l'ouverture d'une porte de voiture et d'une porte de maison. D'ailleurs, on utilisera cet exemple pour montrer le code utilisant le design pattern Bridge.

Exemple de Bridge

// implementator's part
interface Key{
public void open();

public void close();
}

class CarDoorKey implements Key{
@Override
public void open(){
System.out.println("> I'm pushing down the button of remote control.");
}

@Override
public void close(){
System.out.println("> I'm pushing up the button of remote control.");
}
}

class HouseDoorKey implements Key{
@Override
public void open(){
System.out.println("> I'm turning the key in the right side.");
}

@Override
public void close(){
System.out.println("> I'm turning the key in the left side.");
}
}

Notre problème d'ouverture d'une porte est décomposé en deux parties : l'implémentation (clé) et l'abstraction (porte). L'implémentation est une simple interface qu'utilisent deux classes. La première est la clé pour la porte d'une maison. La deuxième correspond à la clé de la porte d'une voiture. Des choses un peu plus intéressantes se passent du côté de l'abstraction.


// abstraction's part
abstract class Door{
private final Key key;

public Door(final Key key){
this.key=key;
}

public void openTheDoor(){
this.key.open();
}

public void closeTheDoor(){
this.key.close();
}

protected void preventOwner(){
System.out.println("> Hi Owner ! You have a geust.");
}
}

class HouseOneDoor extends Door{
public HouseOneDoor(final Key key){
super(key);
}

public boolean enter(){
preventOwner();
openTheDoor();
return true;
}

public boolean leave(){
closeTheDoor();
return true;
}
}

class CarOneDoor extends Door{
public CarOneDoor(final Key key){
super(key);
}

public boolean enter(){
openTheDoor();
return true;
}

public boolean leave(){
closeTheDoor();
return true;
}
}

Dans la partie de l'abstraction, on a les illustrations des portes : celle d'une maison et celle d'une voiture. Cependant, les deux ne se comportent pas exactement de la même manière. Dans le cas d'une porte maison, on prévient le propriétaire des lieux qu'une nouvelle personne est entrée (méthode preventOwner()). On ne le fait pas pour la porte de la voiture. Ceci illustre bien le fait que l'implémentation et l'abstraction vivent chacune leurs vies. Rajout du méthode preventOwner() dans HouseOneDoor n'a pas provoqué l'ajout de la même méthode dans les classes implémentant l'interface Key.

Et pour voir comment cela fonctionne, voici la classe client et, tout de suite après, les messages affichés sur l'écran au moment du test :

final Key carDoorKey=new CarDoorKey();
final Key houseDoorKey=new HouseDoorKey();
final HouseOneDoor oneDoorCar=new HouseOneDoor(houseDoorKey);
System.out.println("House test");
oneDoorCar.enter();
final CarOneDoor carOneDoor=new CarOneDoor(carDoorKey);
System.out.println("Car test");
carOneDoor.enter();



House test
> Hi Owner ! You have a geust.
> I'm turning the key in the right side.
Car test
> I'm pushing down the button of remote control.



Ce patron de conception privilégie la composition à l'héritage. On peut le voir si l'on écrit l'exemple ci-dessus avec l'héritage :

interface Key{
public void open();

public void close();
}

class HouseKey implements Key{
@Override
public void open(){
}

@Override
public void close(){
}
}

class CarKey implements Key{
@Override
public void open(){
}

@Override
public void close(){
}
}

class CarBwmKey extends CarKey{
}

class CarAudiKey extends CarKey{
}

class HouseStudioKey extends HouseKey{
}

class HouseLoftKey extends HouseKey{
}

On peut constater que l'héritage multiple aura les effets négatifs sur la lisibilité du code. Déjà ici on dispose de 6 implémentations de l'interface Key situées à de différents niveaux. Grâce à cette absence des dépendances dans le Bridge, les couches d'abstraction et d'implémentation peuvent être étendues séparémment. On est alors sûr que ces modifications n'influencerons pas le fonctionnement de l'autre partie (implémentation pour la manipulation de l'abstraction et l'abstraction pour la manipulation de l'implémentation). Regardons ceci sur une simple image :

  • a) design pattern Bridge

    Door ____Key___
    / \ / \
    HouseOneDoor CarOneDoor HouseDoorKey CarDoorKey


  • b) l'héritage multiple

    _______Key_______
    / \
    HouseKey CarKey
    / \ / \
    HouseLoftKey HouseStudioKey CarAudiKey CarBwmKey




Le patron de conception Bridge est une bonne illustration du code qui privilégie la composition à l'héritage multiple. Grâce à cela, on gagne en lisibilité et en sécurité. Car, le code est indépendant et la modification du côté de l'abstraction n'aura aucune influence sur le fonctionnement de l'implémentation, et inversement.
Bartosz KONIECZNY 08-09-2013 17:32 design patterns
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Comment valider les checkboxes avec Symfony2 ?

La validation des checkboxes sous Symfony2 se déroule avec une contrainte appelée ChoiceConstraint.

Voici l'exemple de l'utilisation:

$metadata->addPropertyConstraint('orderPreferedGift', new Choice(array('choices' => Gifts::getGiftTypes(true), 'multiple' => true, 'min' => 1,  'multipleMessage' => "Veuillez choisir au moins un type de cadeau", 'groups' => array('validationGroup'))));


Cette contrainte est très puissante. On peut déterminer par exemple la quantité des champs minimale ou maximale à cocher par utilisateur. Il est également possible de vérifier le types des valeurs.