Domanda per principianti sul modello di progettazione di Decorator


18

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

Quindi ho cercato il decoratore e ho trovato un articolo di Wikipedia su di esso. Ora capisco il concetto del modello Decorator, ma ero un po 'confuso da questo passaggio:

Ad esempio, considera una finestra in un sistema a finestre. Per consentire lo scorrimento del contenuto della finestra, potremmo voler aggiungere barre di scorrimento orizzontali o verticali, come appropriato. Supponiamo che le finestre siano rappresentate da istanze della classe Window e che questa classe non abbia funzionalità per l'aggiunta di barre di scorrimento. È possibile creare una sottoclasse ScrollingWindow che li fornisce oppure creare un ScrollingWindowDecorator che aggiunge questa funzionalità agli oggetti Window esistenti. A questo punto, entrambe le soluzioni andrebbero bene.

Ora supponiamo che desideriamo anche la possibilità di aggiungere bordi alle nostre finestre. Ancora una volta, la nostra classe Window originale non ha supporto. La sottoclasse ScrollingWindow ora presenta un problema, poiché ha effettivamente creato un nuovo tipo di finestra. Se desideriamo aggiungere il supporto dei bordi a tutte le finestre, dobbiamo creare sottoclassi WindowWithBorder e ScrollingWindowWithBorder. Ovviamente, questo problema peggiora con ogni nuova funzionalità da aggiungere. Per la soluzione di decorazione, creiamo semplicemente un nuovo BorderedWindowDecorator: in fase di esecuzione, possiamo decorare le finestre esistenti con ScrollingWindowDecorator o BorderedWindowDecorator o entrambi, a nostro avviso.

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

C'era un'altra riga nell'articolo:

Il motivo decorativo è un'alternativa alla sottoclasse. La sottoclasse aggiunge comportamento al momento della compilazione e la modifica influisce su tutte le istanze della classe originale; la decorazione può fornire un nuovo comportamento in fase di esecuzione per singoli oggetti.

Non capisco dove si dice "... la modifica interessa tutte le istanze della classe originale" - in che modo la sottoclasse cambia la classe genitore? Non è questo il punto della sottoclasse?

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

Senza aver letto questo schema, se avessi dovuto modificare il comportamento in fase di esecuzione per singoli oggetti, avrei probabilmente incorporato alcuni metodi nella super o sottoclasse per abilitare / disabilitare detto comportamento. Per favore, aiutami a capire davvero l'utilità di Decorator e perché il mio modo di pensare per principianti è imperfetto?


apprezzo l'editing, Walter ... a proposito, ho usato "ragazzi" non per escludere le donne, ma come saluto informale.
Jim,

La modifica sarebbe semplicemente quella di rimuovere il saluto in generale. Non è un protocollo SO / SE standard usarne uno nelle domande [non ti preoccupare, non pensiamo che sia scortese passare direttamente alla domanda]
Farrell,

Figo, grazie! è stato solo che l'ha taggato "rimuovendo il genere" e odio chiunque pensi che io sia misogino o qualcosa del genere !! :)
Jim,

Li uso pesantemente per evitare quelle cose procedurali legali chiamate "servizi": medium.com/@wrong.about/…
Zapadlo

Risposte:


13

Il motivo decorativo è uno che privilegia la composizione rispetto all'eredità [un altro paradigma OOP che è utile conoscere]

Il vantaggio principale del motivo decorativo - rispetto alla sottoclasse è consentire più opzioni di combinazione e abbinamento. Se ad esempio hai 10 comportamenti diversi che una finestra può avere, questo significa - con la sottoclasse - che devi creare ogni combinazione diversa, che includerà inevitabilmente anche un sacco di riutilizzo del codice.
Tuttavia, cosa succede quando decidi di aggiungere un nuovo comportamento?

Con il decoratore, aggiungi semplicemente una nuova classe che descrive questo comportamento, e il gioco è fatto: il modello ti consente di inserirlo efficacemente senza alcuna modifica al resto del codice.
Con la sottoclasse, hai un incubo tra le mani.
Una domanda che hai posto è stata "in che modo la sottoclasse modifica 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 #, per esempio, usando il newcomando]. Ciò a cui si riferisce è che quando aggiungi queste modifiche a una classe, non hai scelta per far sì che tale modifica sia presente, anche se in realtà non ne hai bisogno.

In alternativa, puoi mettere tutte le sue funzionalità in una singola classe con l'attivazione / disattivazione tramite flag ... ma questo finisce con una singola classe che diventa sempre più grande man mano che il tuo progetto cresce.
Non è inusuale iniziare il tuo progetto in questo modo e trasformarlo in uno schema decorativo quando colpisci una massa critica efficace.

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

Il punto principale del modello è abilitare le modifiche del runtime: potresti non sapere come vuoi che la finestra appaia fino a quando il programma non è in esecuzione, e questo ti consente 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 nelle classi sigillate / finali o fornite da altre API


2
Grazie, Farrell - quando dici "In alternativa, puoi mettere tutte le sue funzionalità in una singola classe con l'attivazione / disattivazione tramite flag ... ma questo finisce con una singola classe che diventa sempre più grande man mano che il tuo progetto cresce." che mi ha fatto cliccare per me. Penso che parte del problema sia che non ho mai lavorato su un corso così grande che il decoratore avesse senso per me. Pensando in grande, posso sicuramente vedere i benefici ... grazie!
Jim,

Inoltre, la parte sulle classi sigillate ha un sacco di senso ... grazie!
Jim,

3

Considera le possibilità di aggiungere barre di scorrimento e bordi con la sottoclasse. Se vuoi ogni 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ò sovrascrivere o meno 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 Windowcomportamento della classe. Una soluzione sarebbe quella di copiare e incollare il codice dalle superclasse nelle classi derivate e adattarlo alle esigenze delle classi derivate, ma ogni modifica in una superclasse deve essere nuovamente incollata e regolata di nuovo, quindi le classi derivate sono strettamente accoppiato alla classe base (tramite la necessità di copia-incolla-aggiusta). 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):
    ...

Ciò significa che per un insieme di caratteristiche reciprocamente indipendenti f con | f | essendo il numero di funzioni, devi definire 2 ** | f | classi, ad es. se hai 10 caratteristiche ottieni 1024 classi strettamente coulped. Se usi il modello Decoratore, ogni funzione ha la sua classe indipendente e liberamente accoppiata e hai solo 1 + | f | classi (sono 11 per l'esempio sopra).


Vedi, il mio pensiero non sarebbe quello di continuare ad aggiungere classi - sarebbe aggiungere la funzionalità alla (sotto) classe originale. quindi nella classe Window (oggetto): hai scrollBar = true, border = false, ecc ... La risposta Farrell mi mostra dove ciò può andare storto, tuttavia - solo portando a classi gonfie e simili ... grazie per la risposta, +1!
Jim,

bene, +1 una volta ho il rappresentante per farlo!
Jim,

2

Non sono un esperto di questo particolare modello, ma a mio modo di vedere il modello Decoratore può essere applicato a classi che potresti non avere la possibilità di modificare o sottoclasse (potrebbero non essere il tuo codice ed essere sigillate, ad esempio ). Nel tuo esempio, cosa succede se non hai scritto la classe Window ma la stai semplicemente consumando? Fintanto che la classe Window ha un'interfaccia e programmi contro quella interfaccia, Decorator può usare la stessa interfaccia ma estendere la funzionalità.

L'esempio che menzioni in realtà è trattato qui abbastanza chiaramente:

L'estensione della funzionalità di un oggetto può essere eseguita staticamente (in fase di compilazione) utilizzando l'ereditarietà, tuttavia potrebbe essere necessario estendere la funzionalità di un oggetto in modo dinamico (in fase di esecuzione) quando viene utilizzato un oggetto.

Considera l'esempio tipico di una finestra grafica. Per estendere la funzionalità della finestra grafica, ad esempio aggiungendo un frame alla finestra, richiederebbe l'estensione della classe window per creare una classe FramedWindow. Per creare una finestra con cornice è necessario creare un oggetto della classe FramedWindow. Tuttavia, sarebbe impossibile iniziare con una finestra semplice ed estenderne la funzionalità in fase di esecuzione per diventare una finestra con cornice.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.