Question de débutant sur le modèle de conception de décorateur

18

Je lisais un article de programmation et il mentionnait le modèle Decorator. Je programme depuis un certain temps, mais sans aucune sorte d'éducation ou de formation formelle, mais j'essaie d'en apprendre davantage sur les modèles standard et autres.

J'ai donc recherché le décorateur et trouvé un article Wikipedia à ce sujet. Je comprends maintenant le concept du motif Decorator, mais j'étais un peu confus par ce passage:

Par exemple, considérons une fenêtre dans un système de fenêtrage. Pour permettre le défilement du contenu de la fenêtre, nous pouvons souhaiter lui ajouter des barres de défilement horizontales ou verticales, selon le cas. Supposons que les fenêtres sont représentées par des instances de la classe Window et supposons que cette classe n'a aucune fonctionnalité pour ajouter des barres de défilement. Nous pourrions créer une sous-classe ScrollingWindow qui les fournit, ou nous pourrions créer un ScrollingWindowDecorator qui ajoute cette fonctionnalité aux objets Window existants. À ce stade, l'une ou l'autre solution conviendrait.

Supposons maintenant que nous souhaitons également pouvoir ajouter des bordures à nos fenêtres. Encore une fois, notre classe Window d'origine ne prend pas en charge. La sous-classe ScrollingWindow pose maintenant un problème, car elle a effectivement créé un nouveau type de fenêtre. Si nous souhaitons ajouter un support de bordure à toutes les fenêtres, nous devons créer des sous-classes WindowWithBorder et ScrollingWindowWithBorder. De toute évidence, ce problème s'aggrave avec chaque nouvelle fonctionnalité à ajouter. Pour la solution de décoration, nous créons simplement un nouveau BorderedWindowDecorator - au moment de l'exécution, nous pouvons décorer les fenêtres existantes avec le ScrollingWindowDecorator ou le BorderedWindowDecorator ou les deux, comme bon nous semble.

OK, quand ils disent d'ajouter des bordures à toutes les fenêtres, pourquoi ne pas simplement ajouter des fonctionnalités dans la classe Window d'origine pour permettre l'option? Pour moi, le sous-classement consiste simplement à ajouter des fonctionnalités spécifiques à une classe ou à remplacer une méthode de classe. Si j'avais besoin d'ajouter des fonctionnalités à tous les objets existants, pourquoi ne modifierais-je pas simplement la superclasse pour le faire?

Il y avait une autre ligne dans l'article:

Le motif décorateur est une alternative au sous-classement. Le sous-classement ajoute un comportement au moment de la compilation et la modification affecte toutes les instances de la classe d'origine; la décoration peut fournir un nouveau comportement au moment de l'exécution pour des objets individuels.

Je n'arrive pas là où ils disent "... le changement affecte toutes les instances de la classe d'origine" - comment le sous-classement change-t-il la classe parente? N'est-ce pas là tout l'intérêt du sous-classement?

Je vais supposer que l'article, comme de nombreux Wiki, n'est tout simplement pas écrit clairement. Je peux voir l'utilité du décorateur dans cette dernière ligne - "... fournir un nouveau comportement au moment de l'exécution pour les objets individuels."

Sans avoir lu ce modèle, si j'avais besoin de changer le comportement au moment de l'exécution pour des objets individuels, j'aurais probablement intégré certaines méthodes dans la super ou sous-classe pour activer / désactiver ledit comportement. S'il vous plaît, aidez-moi à vraiment comprendre l'utilité du décorateur et pourquoi ma pensée de débutant est défectueuse?

Jim
la source
appréciez le montage, Walter ... fyi, j'ai utilisé "gars" non pas pour exclure les femmes, mais comme une salutation informelle.
Jim
La modification consiste simplement à supprimer le message d'accueil en général. Ce n'est pas un protocole SO / SE standard d'en utiliser un dans les questions [ne vous inquiétez pas, nous ne pensons pas qu'il soit impoli de sauter directement dans la question]
Farrell
cool merci! c'est juste qu'il l'a étiqueté "enlever le genre" et je détesterais quelqu'un de penser que je suis misogyne ou quelque chose !! :)
Jim
Je les utilise beaucoup pour éviter ces choses procédurales légales appelées "services": medium.com/@wrong.about/…
Zapadlo

Réponses:

13

Le modèle de décorateur est celui qui privilégie la composition plutôt que l'héritage [un autre paradigme de POO qui est utile pour en savoir plus]

Le principal avantage du motif décorateur - par rapport au sous-classement est de permettre plus d'options de mix & match. Si vous avez, par exemple, 10 comportements différents qu'une fenêtre peut avoir, cela signifie - avec le sous-classement - que vous devez créer chaque combinaison différente, ce qui inclura également inévitablement beaucoup de réutilisation de code.
Cependant, que se passe-t-il lorsque vous décidez d'ajouter un nouveau comportement?

Avec le décorateur, vous ajoutez simplement une nouvelle classe qui décrit ce comportement, et c'est tout - le modèle vous permet de le déposer efficacement sans aucune modification dans le reste du code.
Avec la sous-classification, vous avez un cauchemar entre vos mains.
Une question que vous avez posée était "comment le sous-classement change-t-il la classe parente?" Ce n'est pas que cela change la classe parente; quand il dit une instance, cela signifie tout objet que vous avez «instancié» [si vous utilisez Java ou C #, par exemple, en utilisant la newcommande]. Ce à quoi il fait référence, c'est que lorsque vous ajoutez ces modifications à une classe, vous n'avez pas le choix pour que cette modification soit là, même si vous n'en avez pas réellement besoin.

Alternativement, vous pouvez mettre toutes ses fonctionnalités dans une seule classe avec elle activée / désactivée via des drapeaux ... mais cela se termine par une seule classe qui devient de plus en plus grande à mesure que votre projet se développe.
Il n'est pas inhabituel de commencer votre projet de cette façon et de refaçonner un modèle de décorateur une fois que vous avez atteint une masse critique efficace.

Un point intéressant à souligner: vous pouvez ajouter plusieurs fois la même fonctionnalité; Ainsi, vous pourriez, par exemple, avoir une fenêtre avec double, triple ou tout autre montant ou bordures selon vos besoins.

Le point principal du modèle est d'activer les modifications au moment de l'exécution: vous ne savez peut-être pas à quoi ressemble la fenêtre jusqu'à ce que le programme soit en cours d'exécution, ce qui vous permet de la modifier facilement. Certes, cela peut être fait via le sous-classement, mais pas aussi bien.
Et enfin, il permet d'ajouter des fonctionnalités à des classes que vous ne pourrez peut-être pas modifier - par exemple dans des classes scellées / finales ou fournies par d'autres API

Farrell
la source
2
Merci, Farrell - lorsque vous dites "Alternativement, vous pouvez mettre toutes ses fonctionnalités dans une seule classe avec elle activée / désactivée via des indicateurs ... mais cela se termine par une seule classe qui devient de plus en plus grande à mesure que votre projet se développe." qui l'a fait cliquer pour moi. Je pense qu'une partie du problème est que je n'ai jamais travaillé sur une classe qui était si grande que le décorateur avait du sens pour moi. En pensant grand, je peux certainement voir les avantages ... merci!
Jim
De plus, la partie sur les cours scellés a beaucoup de sens ... merci!
Jim
3

Considérez les possibilités d'ajouter des barres de défilement et des bordures avec le sous-classement. Si vous voulez chaque possibilité, vous obtenez quatre classes (Python):

class Window(object):
    def draw(self):
        "do actual drawing of window"

class WindowWithScrollBar(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of scrollbar"

class WindowWithBorder(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of border"

class WindowWithScrollBarAndBorder(Window):
    def draw(self):
        WindowWithScrollBar.draw(self)
        WindowWithBorder.draw(self)

Maintenant, dans WindowWithScrollBarAndBorder.draw(), la fenêtre est dessinée deux fois, et la deuxième fois, elle peut ou non remplacer la barre de défilement déjà dessinée, selon l'implémentation. Votre code dérivé est donc étroitement lié à l'implémentation d'une autre classe, et vous devez vous en soucier chaque fois que vous modifiez le Windowcomportement de la classe. Une solution serait de copier-coller le code des super classes dans les classes dérivées et de l'ajuster aux besoins des classes dérivées, mais chaque changement dans une super classe doit être recopié et ajusté à nouveau, donc les classes dérivées sont à nouveau étroitement couplé à la classe de base (via la nécessité de copier-coller-ajuster). Un autre problème est que si vous avez besoin d'une autre propriété qu'une fenêtre peut avoir ou non, vous devez doubler chaque classe:

class Window(object):
    ...

class WindowWithGimmick(Window):
    ...

class WindowWithScrollBar(Window):
    ...

class WindowWithBorder(Window):
    ...

class WindowWithScrollBarAndBorder(Window):
    ...

class WindowWithScrollBarAndGimmick(Window):
    ...

class WindowWithBorderAndGimmick(Window):
    ...

class WindowWithScrollBarAndBorderAndGimmick(Window):
    ...

Cela signifie que pour un ensemble de fonctionnalités mutuellement indépendantes f avec | f | étant le nombre de fonctionnalités, vous devez définir 2 ** | f | des classes, par exemple. si vous avez 10 fonctionnalités, vous obtenez 1024 classes étroitement coulped. Si vous utilisez le modèle de décorateur, chaque fonction a sa propre classe indépendante et faiblement couplée et vous n'avez que 1 + | f | (c'est 11 pour l'exemple ci-dessus).

pillmuncher
la source
Vous voyez, ma pensée ne serait pas de continuer à ajouter des classes - ce serait d'ajouter la fonctionnalité à la (sous) classe d'origine. donc dans la classe Window (object): vous avez scrollBar = true, border = false, etc ... La réponse Farrell me montre cependant où cela peut mal tourner - menant juste à des classes gonflées et autres ... merci pour la réponse, +1!
Jim
eh bien, +1 une fois que j'ai le représentant pour le faire!
Jim
2

Je ne suis pas un expert de ce modèle particulier, mais comme je le vois, le modèle Decorator peut être appliqué à des classes que vous n'avez peut-être pas la possibilité de modifier ou de sous-classe (elles peuvent ne pas être votre code et être scellées, par exemple ). Dans votre exemple, que se passe-t-il si vous n'avez pas écrit la classe Window mais que vous la consommez simplement? Tant que la classe Window a une interface et que vous programmez contre cette interface, votre décorateur peut utiliser la même interface mais étendre les fonctionnalités.

L'exemple que vous mentionnez est en fait très bien couvert ici :

L'extension de la fonctionnalité d'un objet peut être effectuée de manière statique (au moment de la compilation) en utilisant l'héritage, mais il peut être nécessaire d'étendre la fonctionnalité d'un objet de manière dynamique (au moment de l'exécution) lors de l'utilisation d'un objet.

Prenons l'exemple typique d'une fenêtre graphique. Pour étendre les fonctionnalités de la fenêtre graphique, par exemple en ajoutant un cadre à la fenêtre, il faudrait étendre la classe window pour créer une classe FramedWindow. Pour créer une fenêtre encadrée, il est nécessaire de créer un objet de la classe FramedWindow. Cependant, il serait impossible de commencer avec une fenêtre simple et d'étendre ses fonctionnalités au moment de l'exécution pour devenir une fenêtre encadrée.

http://www.oodesign.com/decorator-pattern.html

Dan Diplo
la source