Comprendre le modèle de conception du pont

24

Je ne comprends pas du tout le modèle de conception "pont". J'ai parcouru divers sites Web, mais ils n'ont pas aidé.

Quelqu'un peut-il m'aider à comprendre cela?


la source
2
Je ne le comprends pas non plus. Au plaisir de voir les réponses :)
Violet Giraffe
Il existe de nombreux sites Web et livres décrivant les modèles de conception. Je ne pense pas qu'il soit utile de répéter ce qui a déjà été écrit, peut-être pouvez-vous poser une question spécifique. Il m'a fallu un certain temps pour le comprendre aussi, en continuant à basculer entre différentes sources et exemples.
Helena

Réponses:

16

Dans la POO, nous utilisons le polymorphisme de sorte qu'une abstraction peut avoir plusieurs implémentations. Regardons l'exemple suivant:

//trains abstraction
public interface Train
{ 
    move();
}
public class MonoRail:Train
{
    public override move()
    {
        //use one track;
    }
}
public class Rail:Train
{
    public override move()
    {
        //use two tracks;
    }
}

Une nouvelle exigence a été introduite et doit apporter la perspective d'accélération des trains, alors changez le code comme ci-dessous.

    public interface Train
    { 
        void move();
    }
    public class MonoRail:Train
    {
        public override void move()
        {
            //use one track;
        }
    }
    public class ElectricMonoRail:MonoRail
    {
        public override void move()
        {
            //use electric engine on one track.
        }
    }
    public class DieselMonoRail: MonoRail
    {
        public override void move()
        {
            //use diesel engine on one track.
        }
    }
    public class Rail:Train
    {
        public override void move()
        {
            //use two tracks;
        }
    }
    public class ElectricRail:Rail
    {
        public override void move()
        {
            //use electric engine on two tracks.
        }
    }
    public class DieselRail: Rail
    {
        public override void move()
        {
            //use diesel engine on two tracks.
        }
    }

Le code ci-dessus n'est pas maintenable et manque de réutilisabilité (en supposant que nous pourrions réutiliser le mécanisme d'accélération pour la même plate-forme de voie). Le code suivant applique le modèle de pont et sépare les deux abstractions différentes, le transport ferroviaire et l' accélération .

public interface Train
{ 
    void move(Accelerable engine);
}
public interface Accelerable
{
    public void accelerate();
}
public class MonoRail:Train
{
    public override void move(Accelerable engine)
    {
        //use one track;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class Rail:Train
{
    public override void move(Accelerable engine)
    {
        //use two tracks;
        engine.accelerate(); //engine is pluggable (runtime dynamic)
    }
}
public class ElectricEngine:Accelerable{/*implementation code for accelerable*/}
public class DieselEngine:Accelerable{/*implementation code for accelerable*/}
Thurein
la source
3
Très bel exemple. J'ajouterais mes deux cents: c'est un très bon exemple de préférer la composition à l'héritage
zzfima
1
Pour ce que ça vaut, je pense que ça devrait l'être Monorailcar ce n'est pas vraiment deux mots, c'est un seul mot (composé). Un MonoRail serait une sous-classe de Rail au lieu d'un autre type de rail (ce qu'il est). Tout comme nous n'utiliserions pas SunShineou CupCake, ils le seraient SunshineetCupcake
ErikE
Cette ligne "les deux abstractions différentes, le transport ferroviaire et l'accélération" aide, souligne ce que sont les deux hiérarchies et ce que nous essayons de résoudre, c'est de les faire varier indépendamment.
wangdq
11

Bien que la plupart des modèles de conception aient des noms utiles, je trouve que le nom "Bridge" n'est pas intuitif en ce qui concerne ce qu'il fait.

Conceptuellement, vous poussez les détails d'implémentation utilisés par une hiérarchie de classes dans un autre objet, généralement avec sa propre hiérarchie. Ce faisant, vous supprimez une dépendance étroite à l'égard de ces détails d'implémentation et autorisez les détails de cette implémentation à changer.

À petite échelle, je compare cela à l'utilisation d'un modèle de stratégie dans la façon dont vous pouvez brancher un nouveau comportement. Mais au lieu de simplement encapsuler un algorithme comme on le voit souvent dans une stratégie, l'objet d'implémentation est généralement plus rempli de fonctionnalités. Et lorsque vous appliquez le concept à une hiérarchie de classes entière, le modèle plus grand devient un pont. (Encore une fois, déteste le nom).

Ce n'est pas un modèle que vous utiliserez tous les jours, mais je l'ai trouvé utile lors de la gestion d'une explosion potentielle de classes qui peut se produire lorsque vous avez un besoin (apparent) d'héritage multiple.

Voici un exemple concret:

J'ai un outil RAD qui vous permet de déposer et de configurer des contrôles sur une surface de conception, j'ai donc un modèle d'objet comme celui-ci:

Widget // base class with design surface plumbing
+ Top
+ Left
+ Width
+ Height
+ Name
+ SendToBack
+ BringToFront
+ OnPropertyEdit
+ OnSelect
+ Validate
+ ShowEditor
+ Paint
+ Etc

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor // override base to show a property editor form specific to a Textbox
+ Paint // override to render a textbox onto the surface    
+ Etc

ListWidget : Widget // list specific
+ Items
+ SelectedItem
+ ShowEditor // override base to show a property editor form specific to a List
+ Paint // override to render a list onto the surface
+ Etc

Et ainsi de suite, avec peut-être une dizaine de commandes.

Mais une nouvelle exigence est ajoutée pour prendre en charge plusieurs thèmes (look-n-feel). Disons que nous avons les thèmes suivants: Win32, WinCE, WinPPC, WinMo50, WinMo65. Chaque thème aurait différentes valeurs ou implémentations pour les opérations liées au rendu comme DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb, etc.

Je pourrais créer un modèle d'objet comme celui-ci:

Win32TextboxWidget : TextboxWidget

Win32ListWidget : ListWidget

etc., pour un type de contrôle

WinCETextboxWidget : TextboxWidget

WinCEListWidget : ListWidget

etc., pour chaque autre type de contrôle (à nouveau)

Vous avez l'idée - vous obtenez une explosion de classe du nombre de widgets fois le nombre de thèmes. Cela complique le concepteur RAD en lui faisant prendre conscience de chaque thème. De plus, l'ajout de nouveaux thèmes oblige le concepteur RAD à être modifié. De plus, il y a beaucoup d'implémentation commune dans un thème qu'il serait génial d'hériter, mais les contrôles héritent déjà d'une base commune ( Widget).

Donc, ce que j'ai fait, c'est créer une hiérarchie d'objets distincte qui implémente le thème. Chaque widget contiendrait une référence à l'objet qui implémente les opérations de rendu. Dans de nombreux textes, cette classe est suffixée avec un Implmais j'ai dévié de cette convention de dénomination.

Alors maintenant, mon TextboxWidgetlook ressemble à ceci:

TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor
+ Painter // reference to the implementation of the widget rendering operations
+ Etc

Et je peux faire hériter mes différents peintres de ma base thématique, ce que je ne pouvais pas faire auparavant:

Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc

Win32TextboxPainter : Win32WidgetPainter

Win32ListPainter : Win32WidgetPainter

L'une des bonnes choses est que je peux charger dynamiquement les implémentations au moment de l'exécution, ce qui me permet d'ajouter autant de thèmes que je veux sans changer le logiciel de base. En d'autres termes, ma "mise en œuvre peut varier indépendamment de l'abstraction".

tcarvin
la source
Je ne comprends pas comment cela est supposé être le motif du pont? Lorsque vous ajoutez un nouveau composant à la hiérarchie des widgets, vous êtes obligé d'ajouter ce nouveau widget à TOUS les peintres (Win32NewWidgetPainter, PPCNewWidgetPainter). Ce ne sont PAS deux hiérarchies en croissance indépendante. Pour un modèle de pont approprié, vous ne devez pas sous-classer la classe PlatformWidgetPainter pour chaque widget, plutôt lui faire recevoir un "descripteur de dessin" de widget.
Mazyod
Merci pour la rétroaction, vous avez raison. Il y a des années, j'ai posté cela, et en le révisant maintenant, je dirais qu'il décrit bien le pont jusqu'au dernier morceau d'où je suis issu Win32TextboxPainteret Win32ListPainter d' où je viens Win32WidgetPainter. Vous pouvez avoir un arbre d'héritage du côté de l'implémentation, mais il devrait être plus générique (peut StaticStyleControlPainter- être EditStyleControlPainter, et ButtonStyleControlPainter) avec toutes les opérations primitives nécessaires remplacées si nécessaire. C'est plus proche du vrai code sur lequel je basais l'exemple.
tcarvin
3

Le pont a l'intention de dissocier une abstraction de sa mise en œuvre concrète , afin que les deux puissent varier indépendamment:

  • affiner l'abstraction avec le sous-classement
  • fournir différentes implémentations, également par sous-classement, sans avoir à connaître ni l'abstraction ni ses raffinements.
  • si nécessaire, choisissez au moment de l'exécution la mise en œuvre la plus appropriée .

Le pont y parvient en utilisant la composition:

  • l'abstraction fait référence (référence ou pointeur) à un objet d'implémentation
  • l'abstraction et ses raffinements ne connaissaient que l'interface de mise en œuvre

entrez la description de l'image ici

Remarques supplémentaires sur une confusion fréquente

Ce modèle est très similaire au modèle d'adaptateur: l' abstraction offre une interface différente pour une implémentation et utilise la composition pour ce faire. Mais:

La principale différence entre ces modèles réside dans leurs intentions
- Gamma & al, dans " Design patterns, element of reusable OO software " , 1995

Dans cet excellent ouvrage fondateur sur les modèles de conception, les auteurs observent également que:

  • les adaptateurs sont souvent utilisés lorsqu'une incompatibilité est découverte et que le couplage est imprévu
  • les ponts sont utilisés dès le début de la conception, lorsque l'on s'attend à ce que les classes évoluent indépendamment.
Christophe
la source