Quand utilisez-vous le modèle de pont? En quoi est-ce différent du modèle d'adaptateur?

154

Quelqu'un at-il déjà utilisé le modèle de pont dans une application du monde réel? Si oui, comment l'avez-vous utilisé? Est-ce moi, ou est-ce juste le modèle d'adaptateur avec une petite injection de dépendance jetée dans le mélange? Mérite-t-il vraiment son propre modèle?

Charles Graham
la source
Veuillez envisager d'accepter une réponse différente à cette question. La réponse actuellement acceptée est incorrecte et inutile. Les nouvelles réponses sont de loin supérieures.
jaco0646 le
Le livre du GoF répond directement à cette question.
jaco0646

Réponses:

76

Un exemple classique du modèle de pont est utilisé dans la définition de formes dans un environnement d'interface utilisateur (voir l'entrée Wikipedia du modèle de pont ). Le modèle Bridge est un composite des modèles de modèle et de stratégie .

Il s'agit d'une vue commune de certains aspects du modèle d'adaptateur dans le modèle de pont. Cependant, pour citer cet article :

À première vue, le modèle Bridge ressemble beaucoup au modèle Adapter dans la mesure où une classe est utilisée pour convertir un type d'interface en un autre. Cependant, l'intention du modèle d'adaptateur est de donner à une ou plusieurs interfaces de classes la même apparence que celle d'une classe particulière. Le modèle Bridge est conçu pour séparer l'interface d'une classe de son implémentation afin que vous puissiez modifier ou remplacer l'implémentation sans changer le code client.

shek
la source
1
Bridge n'a rien à voir avec le modèle ou la stratégie. Le pont est un modèle structurel. Le modèle et la stratégie sont des modèles de comportement.
jaco0646 le
249

Il y a une combinaison des réponses de Federico et de John .

Quand:

                   ----Shape---
                  /            \
         Rectangle              Circle
        /         \            /      \
BlueRectangle  RedRectangle BlueCircle RedCircle

Refactoriser en:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red
Anton Shchastnyi
la source
6
Pourquoi feriez-vous l'héritage des couleurs?
vainolo
10
@vainolo parce que la couleur est une interface et le bleu, le rouge sont des couleurs concrètes
Weltschmerz
3
Ceci est juste une refactorisation. Intention du modèle de pont: "Découpler une abstraction de son implémentation afin que les deux puissent varier indépendamment." Où est l'abstraction et où est la mise en œuvre ici?
clapas le
1
Le rectangle (couleur) n'est-il pas plus abstrait que le BlueRectangle?
Anton Shchastnyi
2
@clapas, Abstraction est "Shape.color" de la propriété, donc la classe Red et la classe Blue sont l'implémentation, et l'interface Color est le pont.
reco du
230

Le modèle Bridge est une application du vieux conseil, "préférez la composition à l'héritage". Cela devient pratique lorsque vous devez sous-classer différents moments de manière orthogonale les uns par rapport aux autres. Disons que vous devez implémenter une hiérarchie de formes colorées. Vous ne sous-classeriez pas Shape avec Rectangle et Circle, puis sous-classeriez Rectangle avec RedRectangle, BlueRectangle et GreenRectangle et la même chose pour Circle, n'est-ce pas? Vous préféreriez dire que chaque forme a une couleur et implémenter une hiérarchie de couleurs, et c'est le modèle de pont. Eh bien, je n'implémenterais pas une "hiérarchie de couleurs", mais vous voyez l'idée ...

Federico A. Ramponi
la source
1
Voir également le diagramme d'Anton Shchastnyi ci-dessous pour une illustration graphique de cette explication.
NomadeNumerique
2
Je ne pense pas qu'une couleur soit un bon exemple pour une hiérarchie d'implémentation, c'est plutôt déroutant. Il y a un bon exemple du modèle Bridge dans "Design patterns" du GoF, où la mise en œuvre dépend de la plate-forme: PM d'IBM, X d'UNIX, etc.
clapas
215

Quand:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

Refactoriser en:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2
John Sonmez
la source
3
Je pense que c'est une approche très pragmatique des modèles: 1) décrire une conception simple sous-optimale 2) refactoriser la conception / le code pour mieux factoriser
Alexey
1
Utilisez le concept mathématique pour expliquer le modèle de conception du pont. Très intéressé.
Jian Huang
1
Ceci est juste une refactorisation. Intention du modèle de pont: "Découpler une abstraction de son implémentation afin que les deux puissent varier indépendamment." Où est l'abstraction et où est la mise en œuvre ici?
clapas le
John le met bien dans un article de blog . J'ai trouvé que c'était une bonne lecture pour une vue d'ensemble de haut niveau.
Vaibhav Bhalla
29

L'adaptateur et le pont sont certainement liés, et la distinction est subtile. Il est probable que certaines personnes qui pensent utiliser l'un de ces modèles utilisent en fait l'autre modèle.

L'explication que j'ai vue est que Adapter est utilisé lorsque vous essayez d'unifier les interfaces de certaines classes incompatibles qui existent déjà . L'adaptateur fonctionne comme une sorte de traducteur d'implémentations qui pourraient être considérées comme héritées .

Alors que le modèle Bridge est utilisé pour le code qui est plus susceptible d'être greenfield. Vous concevez le Bridge pour fournir une interface abstraite pour une implémentation qui doit varier, mais vous définissez également l'interface de ces classes d'implémentation.

Les pilotes de périphériques sont un exemple souvent cité de Bridge, mais je dirais que c'est un Bridge si vous définissez la spécification d'interface pour les fournisseurs de périphériques, mais c'est un adaptateur si vous prenez des pilotes de périphérique existants et créez une classe wrapper pour fournir une interface unifiée.

Donc, au niveau du code, les deux modèles sont très similaires. Sur le plan commercial, ils sont différents.

Voir aussi http://c2.com/cgi/wiki?BridgePattern

Bill Karwin
la source
Salut Bill. Je ne comprends pas pourquoi nous utilisons nécessairement le modèle Bridge dans les pilotes de périphériques. Je veux dire que nous pouvons facilement déléguer la mise en œuvre (de lecture, d'écriture, de recherche, etc.) à la bonne classe via le polymorphisme, n'est-ce pas? Ou avec un visiteur peut-être? Pourquoi ça doit être Bridge? Merci d'avance.
stdout
1
@zgulser, oui, vous utilisez le polymorphisme. Le modèle Bridge décrit un type d'utilisation de sous-classes pour découpler l'implémentation de l'abstraction.
Bill Karwin
Vous vouliez dire découpler la mise en œuvre de la forme (c'est-à-dire le rectangle) de l'abstraction des couleurs de jour, non? Et je crois que vous dites qu'il existe différentes façons de le faire et Bridge n'est que l'une d'entre elles.
stdout
Oui, le sous-classement a d'autres utilisations. Cette façon particulière d'utiliser les sous-classes est ce qui en fait le modèle Bridge.
Bill Karwin
Et le découplage que je veux dire est de l'interface de forme abstraite à une implémentation concrète de Rectangle. Ainsi, vous pouvez écrire du code qui a besoin d'un objet du type "Shape", même si l'objet concret est en réalité une sous-classe de Shape.
Bill Karwin
27

D'après mon expérience, Bridge est un modèle assez souvent récurrent, car c'est la solution chaque fois qu'il y a deux dimensions orthogonales dans le domaine . Par exemple, les formes et les méthodes de dessin, les comportements et les plates-formes, les formats de fichiers et les sérialiseurs, etc.

Et un conseil: pensez toujours aux modèles de conception du point de vue conceptuel , pas du point de vue de la mise en œuvre. Du bon point de vue, Bridge ne peut pas être confondu avec Adapter, car ils résolvent un problème différent, et la composition est supérieure à l'héritage non pas à cause d'elle-même, mais parce qu'elle permet de traiter séparément les problèmes orthogonaux.

thSoft
la source
22

L'intention de Bridge et Adapter est différente et nous avons besoin des deux modèles séparément.

Modèle de pont:

  1. C'est un modèle structurel
  2. L'abstraction et l'implémentation ne sont pas liées au moment de la compilation
  3. Abstraction et mise en œuvre - les deux peuvent varier sans impact sur le client
  4. Utilise la composition plutôt que l'héritage.

Utilisez le modèle Bridge lorsque:

  1. Vous voulez une liaison d'exécution de l'implémentation,
  2. Vous disposez d'une prolifération de classes résultant d'une interface couplée et de nombreuses implémentations,
  3. Vous souhaitez partager une implémentation entre plusieurs objets,
  4. Vous devez mapper des hiérarchies de classes orthogonales.

@ La réponse de John Sonmez montre clairement l'efficacité du modèle de pont pour réduire la hiérarchie des classes.

Vous pouvez vous référer au lien de documentation ci-dessous pour obtenir un meilleur aperçu du modèle de pont avec un exemple de code

Modèle d'adaptateur :

  1. Il permet à deux interfaces indépendantes de fonctionner ensemble à travers les différents objets, jouant éventuellement le même rôle.
  2. Il modifie l'interface d'origine.

Principales différences:

  1. L'adaptateur fait fonctionner les choses après leur conception; Bridge les fait travailler avant qu'ils ne le soient.
  2. Bridge est conçu à l'avance pour permettre à l' abstraction et à l'implémentation de varier indépendamment . L'adaptateur est mis à niveau pour permettre aux classes indépendantes de travailler ensemble.
  3. L'intention: Adapter permet à deux interfaces non liées de fonctionner ensemble. Bridge permet à l'abstraction et à l'implémentation de varier indépendamment.

Question SE associée avec diagramme UML et code de travail:

Différence entre le modèle de pont et le modèle d'adaptateur

Articles utiles:

article de modèle de pont de sourcemaking

article de modèle d' adaptateur de sourcemaking

article de modèle de pont journaldev

ÉDITER:

Exemple de modèle de pont réel (selon la suggestion de meta.stackoverflow.com, exemple de site de documentation incorporé dans cet article puisque la documentation va se coucher)

Le modèle de pont dissocie l'abstraction de l'implémentation afin que les deux puissent varier indépendamment. Il a été réalisé avec la composition plutôt que l'héritage.

Modèle de pont UML de Wikipedia:

Modèle de pont UML de Wikipedia

Vous avez quatre composants dans ce modèle.

Abstraction: Il définit une interface

RefinedAbstraction: Il implémente l'abstraction:

Implementor: Il définit une interface pour l'implémentation

ConcreteImplementor: Il implémente l'interface de l'implémenteur.

The crux of Bridge pattern :Deux hiérarchies de classes orthogonales utilisant la composition (et pas d'héritage). La hiérarchie d'abstraction et la hiérarchie d'implémentation peuvent varier indépendamment. La mise en œuvre ne fait jamais référence à l'abstraction. Abstraction contient une interface d'implémentation en tant que membre (via la composition). Cette composition réduit un niveau supplémentaire de hiérarchie d'héritage.

Cas d'utilisation de mots réels:

Permettez à différents véhicules d'avoir les deux versions de système de vitesse manuel et automatique.

Exemple de code:

/* Implementor interface*/
interface Gear{
    void handleGear();
}

/* Concrete Implementor - 1 */
class ManualGear implements Gear{
    public void handleGear(){
        System.out.println("Manual gear");
    }
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
    public void handleGear(){
        System.out.println("Auto gear");
    }
}
/* Abstraction (abstract class) */
abstract class Vehicle {
    Gear gear;
    public Vehicle(Gear gear){
        this.gear = gear;
    }
    abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
    public Car(Gear gear){
        super(gear);
        // initialize various other Car components to make the car
    }
    public void addGear(){
        System.out.print("Car handles ");
        gear.handleGear();
    }
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
    public Truck(Gear gear){
        super(gear);
        // initialize various other Truck components to make the car
    }
    public void addGear(){
        System.out.print("Truck handles " );
        gear.handleGear();
    }
}
/* Client program */
public class BridgeDemo {    
    public static void main(String args[]){
        Gear gear = new ManualGear();
        Vehicle vehicle = new Car(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Car(gear);
        vehicle.addGear();

        gear = new ManualGear();
        vehicle = new Truck(gear);
        vehicle.addGear();

        gear = new AutoGear();
        vehicle = new Truck(gear);
        vehicle.addGear();
    }
}

production:

Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear

Explication:

  1. Vehicle est une abstraction.
  2. Caret Trucksont deux implémentations concrètes de Vehicle.
  3. Vehicledéfinit une méthode abstraite: addGear().
  4. Gear est l'interface de l'implémenteur
  5. ManualGearet AutoGearsont deux implémentations de Gear
  6. Vehiclecontient l' implementorinterface plutôt que l'implémentation de l'interface. Compositonde l'interface de l'implémenteur est au cœur de ce modèle: il permet à l'abstraction et à l'implémentation de varier indépendamment.
  7. Caret Truckdéfinir l'implémentation (abstraction redéfinie) pour l'abstraction addGear():: Elle contient Gear- Soit ManualsoitAuto

Cas d'utilisation du modèle Bridge :

  1. L'abstraction et l' implémentation peuvent changer indépendamment l'une de l'autre et elles ne sont pas liées au moment de la compilation
  2. Mappez les hiérarchies orthogonales - une pour l' abstraction et une pour la mise en œuvre .
Ravindra babu
la source
«L'adaptateur fait fonctionner les choses après leur conception; Bridge les fait fonctionner avant qu'elles ne le soient.» Vous voudrez peut-être regarder dans l'adaptateur enfichable. Il s'agit d'une variante de l'adaptateur décrite par le GoF dans la section «Adaptateur» de son livre Design Patterns. Le but est de créer une interface pour les classes qui n'existent pas encore. Un adaptateur enfichable n'est pas un pont, donc je ne pense pas que le premier point soit valide.
c1moore
Bien que les engrenages manuels et automatiques puissent nécessiter une mise en œuvre différente pour le camion et la voiture
andigor
9

J'ai utilisé le modèle de pont au travail. Je programme en C ++, où il est souvent appelé l'idiome PIMPL (pointeur vers l'implémentation). Cela ressemble à ceci:

class A
{
public: 
  void foo()
  {
    pImpl->foo();
  }
private:
  Aimpl *pImpl;
};

class Aimpl
{
public:
  void foo();
  void bar();
};  

Dans cet exemple, class Acontient l'interface et class Aimplcontient l'implémentation.

Une utilisation de ce modèle consiste à exposer uniquement certains des membres publics de la classe d'implémentation, mais pas d'autres. Dans l'exemple, seul Aimpl::foo()peut être appelé via l'interface publique de A, mais pasAimpl::bar()

Un autre avantage est que vous pouvez définir Aimpldans un fichier d'en-tête séparé qui n'a pas besoin d'être inclus par les utilisateurs de A. Tout ce que vous avez à faire est d'utiliser une déclaration Aimplavant de Adéfinir avant et de déplacer les définitions de toutes les fonctions membres référençant pImpldans le fichier .cpp. Cela vous permet de garder l'en- Aimpltête privé et de réduire le temps de compilation.

Dima
la source
2
Si vous utilisez ce modèle, l'AImpl n'a même pas besoin d'en-tête. Je viens de le mettre en ligne dans le fichier d'implémentation pour la classe A
1800 INFORMATIONS
Votre réalisateur est privé. J'ai une nouvelle question à ce sujet, voir stackoverflow.com/questions/17680762/…
Roland
7

Pour mettre un exemple de forme dans le code:

#include<iostream>
#include<string>
#include<cstdlib>

using namespace std;

class IColor
{
public:
    virtual string Color() = 0;
};

class RedColor: public IColor
{
public:
    string Color()
    {
        return "of Red Color";
    }
};

class BlueColor: public IColor
{
public:
    string Color()
    {
        return "of Blue Color";
    }
};


class IShape
{
public:
virtual string Draw() = 0;
};

class Circle: public IShape
{
        IColor* impl;
    public:
        Circle(IColor *obj):impl(obj){}
        string Draw()
        {
            return "Drawn a Circle "+ impl->Color();
        }
};

class Square: public IShape
{
        IColor* impl;
    public:
        Square(IColor *obj):impl(obj){}
        string Draw()
        {
        return "Drawn a Square "+ impl->Color();;
        }
};

int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();

IShape* sq = new Square(red);
IShape* cr = new Circle(blue);

cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();

delete red;
delete blue;
return 1;
}

La sortie est:

Drawn a Square of Red Color
Drawn a Circle of Blue Color

Notez la facilité avec laquelle de nouvelles couleurs et formes peuvent être ajoutées au système sans conduire à une explosion des sous-classes en raison des permutations.

NotAgain dit réintégrer Monica
la source
0

pour moi, je pense que c'est un mécanisme où vous pouvez échanger des interfaces. Dans le monde réel, vous pourriez avoir une classe qui peut utiliser plus d'une interface, Bridge vous permet d'échanger.

j2emanue
la source
0

Vous travaillez pour une compagnie d'assurance où vous développez une application de workflow qui gère différents types de tâches: comptabilité, contrat, sinistres. C'est l'abstraction. Côté implémentation, vous devez être capable de créer des tâches à partir de différentes sources: email, fax, e-messagerie.

Vous commencez votre conception avec ces classes:

public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

Désormais, chaque source devant être gérée de manière spécifique, vous décidez de spécialiser chaque type de tâche:

public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}

public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}

public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}

Vous vous retrouvez avec 13 cours. L'ajout d'un type de tâche ou d'un type source devient difficile. L'utilisation du modèle de pont produit quelque chose de plus facile à maintenir en découplant la tâche (l'abstraction) de la source (ce qui est un problème d'implémentation):

// Source
public class Source {
   public string GetSender();
   public string GetMessage();
   public string GetContractReference();
   (...)
}

public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}

// Task
public class Task {
   public Task(Source source);
   (...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

L'ajout d'un type de tâche ou d'une source est désormais beaucoup plus simple.

Remarque: la plupart des développeurs ne créeraient pas la hiérarchie des 13 classes à l'avance pour gérer ce problème. Cependant, dans la vraie vie, vous ne connaissez peut-être pas à l'avance le nombre de types de sources et de tâches; si vous n'avez qu'une seule source et deux types de tâches, vous ne dissocierez probablement pas la tâche de la source. Ensuite, la complexité globale augmente à mesure que de nouvelles sources et types de tâches sont ajoutés. À un moment donné, vous refactoriserez et, le plus souvent, vous vous retrouverez avec une solution de type pont.

Sylvain Rodrigue
la source
-5
Bridge design pattern we can easily understand helping of service and dao layer.

Dao layer -> create common interface for dao layer ->
public interface Dao<T>{
void save(T t);
}
public class AccountDao<Account> implement Dao<Account>{
public void save(Account){
}
}
public LoginDao<Login> implement Dao<Login>{
public void save(Login){
}
}
Service Layer ->
1) interface
public interface BasicService<T>{
    void save(T t);
}
concrete  implementation of service -
Account service -
public class AccountService<Account> implement BasicService<Account>{
 private Dao<Account> accountDao;
 public AccountService(AccountDao dao){
   this.accountDao=dao;
   }
public void save(Account){
   accountDao.save(Account);
 }
}
login service- 
public class LoginService<Login> implement BasicService<Login>{
 private Dao<Login> loginDao;
 public AccountService(LoginDao dao){
   this.loginDao=dao;
   }
public void save(Login){
   loginDao.save(login);
 }
}

public class BridgePattenDemo{
public static void main(String[] str){
BasicService<Account> aService=new AccountService(new AccountDao<Account>());
Account ac=new Account();
aService.save(ac);
}
}
}
Sohan Kumawat
la source
5
J'ai voté contre parce que je pense que c'est une réponse alambiquée et mal formatée.
Zimano
1
Tout à fait d'accord, comment pouvez-vous publier des réponses sur ce site sans une attention minimale à l'indentation et à la clarté du code
Massimiliano Kraus