Domanda newbie sul modello di progettazione Decorator

18

Stavo leggendo un articolo di programmazione e menzionava il pattern Decorator. Ho programmato per un po ', ma senza alcun tipo di istruzione o formazione formale, ma sto cercando di conoscere i modelli standard e simili.

Così ho cercato il Decorator e ho trovato un articolo di Wikipedia su di esso. Ora capisco il concetto del pattern Decorator, ma sono stato un po 'confuso da questo passaggio:

As an example, consider a window in a windowing system. To allow scrolling of the window's contents, we may wish to add horizontal or vertical scrollbars to it, as appropriate. Assume windows are represented by instances of the Window class, and assume this class has no functionality for adding scrollbars. We could create a subclass ScrollingWindow that provides them, or we could create a ScrollingWindowDecorator that adds this functionality to existing Window objects. At this point, either solution would be fine.

Now let's assume we also desire the ability to add borders to our windows. Again, our original Window class has no support. The ScrollingWindow subclass now poses a problem, because it has effectively created a new kind of window. If we wish to add border support to all windows, we must create subclasses WindowWithBorder and ScrollingWindowWithBorder. Obviously, this problem gets worse with every new feature to be added. For the decorator solution, we simply create a new BorderedWindowDecorator—at runtime, we can decorate existing windows with the ScrollingWindowDecorator or the BorderedWindowDecorator or both, as we see fit.

OK, quando dicono di aggiungere bordi a tutte le finestre, perché non aggiungere semplicemente funzionalità nella classe Window originale per consentire l'opzione? Per come la vedo io, la sottoclasse serve solo per aggiungere funzionalità specifiche a una classe o per sostituire un metodo di classe. Se dovessi aggiungere funzionalità a tutti gli oggetti esistenti, perché non dovrei semplicemente modificare la superclasse per farlo?

C'era un'altra riga nell'articolo:

The decorator pattern is an alternative to subclassing. Subclassing adds behavior at compile time, and the change affects all instances of the original class; decorating can provide new behavior at run-time for individual objects.

Non capisco dove dicono "... la modifica riguarda tutte le istanze della classe originale" - come fa la sottoclasse a cambiare la classe genitore? Non è questo il punto della sottoclasse?

Suppongo che l'articolo, come molti Wiki, non sia stato scritto chiaramente. Riesco a vedere l'utilità del decoratore nell'ultima riga: "... fornisce un nuovo comportamento in fase di esecuzione per i singoli oggetti."

Senza aver letto su questo modello, se avessi avuto bisogno di cambiare comportamento in fase di esecuzione per singoli oggetti, avrei probabilmente creato alcuni metodi nella sottoclasse o sottoclasse per abilitare / disabilitare il suddetto comportamento. Per favore aiutami a capire davvero l'utilità del Decoratore, e perché il mio pensiero da principiante è imperfetto?

    
posta Jim 02.06.2011 - 15:47
fonte

3 risposte

13

Il modello decoratore è uno che favorisce la composizione rispetto all'eredità [un altro paradigma OOP che è utile conoscere]

Il principale vantaggio del modello decoratore - rispetto alla sottoclasse è quello di consentire più mix & opzioni di corrispondenza. Se si dispone, ad esempio, di 10 comportamenti diversi che una finestra può avere, allora questo significa - con sottoclassi - è necessario creare ogni combinazione diversa, che includerà inevitabilmente anche molto riutilizzo del codice.
Tuttavia, cosa succede quando decidi di aggiungere un nuovo comportamento?

Con il decoratore, devi solo aggiungere una nuova classe che descrive questo comportamento, e questo è tutto - il pattern ti permette di rilasciarlo efficacemente senza alcuna modifica al resto del codice.
Con la sottoclasse, hai un incubo a portata di mano.
Una domanda che hai posto è stata "come cambia la sottoclasse la classe genitore?" Non è che cambia la classe genitore; quando dice un'istanza, significa qualsiasi oggetto che hai 'istanziato' [se stai usando Java o C #, ad esempio, usando il comando new ]. A cosa si riferisce, quando aggiungi queste modifiche a una classe, non hai altra scelta che il cambiamento sia presente, anche se non ne hai effettivamente bisogno.

In alternativa, puoi mettere tutte le sue funzionalità in una singola classe attivandola / disattivandola tramite flag ... ma questo finisce con una singola classe che diventa sempre più grande man mano che il progetto cresce.
Non è insolito iniziare il tuo progetto in questo modo e rifattorizzare in un modello decoratore una volta raggiunta una massa critica efficace.

Un punto interessante che dovrebbe essere fatto: è possibile aggiungere la stessa funzionalità più volte; quindi potresti, ad esempio, avere una finestra con doppio, triplo o qualsiasi importo o limite di cui hai bisogno.

Il punto principale del pattern è di abilitare le modifiche in fase di esecuzione: potresti non sapere come vuoi che la finestra guardi fino a quando il programma è in esecuzione, e questo ti permette di modificarlo facilmente. Certo, questo può essere fatto tramite la sottoclasse, ma non altrettanto bene.
Infine, consente di aggiungere funzionalità alle classi che potresti non essere in grado di modificare, ad esempio in classi sigillate / finali o fornite da altre API

    
risposta data 02.06.2011 - 16:32
fonte
3

Considera le possibilità di aggiungere barre di scorrimento e bordi con la sottoclasse. Se vuoi tutte le possibilità, ottieni quattro classi (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)

Ora, in WindowWithScrollBarAndBorder.draw() , la finestra viene disegnata due volte e la seconda volta può o non può sovrascrivere la barra di scorrimento già disegnata, a seconda dell'implementazione. Quindi il tuo codice derivato è strettamente associato all'implementazione di un'altra classe, e devi preoccupartene ogni volta che modifichi il comportamento della classe Window . Una soluzione sarebbe copiare e incollare il codice dalle super classi tho le classi derivate e adattarlo alle esigenze delle classi derivate, ma ogni modifica in una super classe deve essere nuovamente copiata e regolata di nuovo, quindi di nuovo le classi derivate sono strettamente accoppiato alla classe base (tramite la necessità di copia-incolla-aggiustare). Un altro problema è che se hai bisogno di un'altra proprietà che una finestra può o non può avere, devi raddoppiare ogni 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):
    ...

Questo significa per un insieme di caratteristiche mutualmente indipendenti f con | f | essendo il numero di funzioni, devi definire 2 ** | f | classi, ad es. se si dispone di 10 funzionalità si ottengono 1024 classi strettamente coulpate. Se si utilizza il pattern Decorator, ogni funzione ottiene la propria classe indipendente e liberamente accoppiata e si ha solo 1 + | f | classi (che è 11 per l'esempio sopra).

    
risposta data 02.06.2011 - 16:37
fonte
2

Non sono un esperto di questo particolare modello, ma dal mio punto di vista il pattern Decorator può essere applicato a classi che potresti non avere la possibilità di modificare o sottoclasse (potrebbero non essere il tuo codice ed essere sigillati , per esempio). Nel tuo esempio, cosa succede se non hai scritto la classe Window ma lo stai solo consumando? Finché la classe Window ha un'interfaccia e tu programmi contro quell'interfaccia, Decorator può usare la stessa interfaccia ma estendere la funzionalità.

L'esempio che citi è in realtà coperto qui in modo abbastanza preciso:

Extending an object's functionality can be done statically (at compile time) by using inheritance however it might be necessary to extend an object's functionality dynamically (at runtime) as an object is used.

Consider the typical example of a graphical window. To extend the functionality of the graphical window for example by adding a frame to the window, would require extending the window class to create a FramedWindow class. To create a framed window it is necessary to create an object of the FramedWindow class. However it would be impossible to start with a plain window and to extend its functionality at runtime to become a framed window.

link

    
risposta data 02.06.2011 - 16:42
fonte