Home > GOF Pattern, java > GoF Patterns: Decorator

GoF Patterns: Decorator

28 febbraio 2011

Translate in English with Google Translate
In questo articolo tratterò il pattern Decorator anche detto Wrapper.

Motivazione

Si tratta di un pattern strutturale basato su oggetti che viene utilizzato per aggiungere a RunTime delle funzionalità ad un oggetto.
In Java, e più in generale nella programmazione ad oggetti, per aggiungere delle funzionalità ad una classe viene utilizzata l’ereditarietà che prevede la creazione di classi figlie che specializzano il comportamento della classe padre ma tutto ciò avviene a CompileTime.

Pertanto se in sede di definizione della struttura delle classi non vengono previste delle specifiche funzionalità, queste non saranno disponibili a RunTime. Al fine di superare questo limite, attraverso la decorazione è possibile aggiungere nuove funzionalità senza dover alterare la struttura delle classi ed i rapporti di parentela in quanto è possibile agire a RunTime per modificare il comportamento di un oggetto.

Per esempio: si vuole conoscere il tempo di esecuzione di un metodo ma tale funzionalità non è prevista nel metodo di nostro interesse. Come fare? Creiamo una classe “Decorator” da invocare al posto della classe originaria e che si occuperà di monitorare il tempo trascorso nell’invocazione del metodo originario. Come? Mantenendo una associazione alla classe originaria e calcolando il tempo di esecuzione del metodo. Vediamo l’esempio in seguito, in sede di implementazione.

Partecipanti e Struttura

Questo pattern è composto dai seguenti partecipanti:

  1. Client: colui che effettua l’invocazione alla funzionalità di interesse
  2. Component: definisce l’interfaccia degli oggetti per i quali verranno aggiunte nuove funzionalità
  3. ConcreteComponent: definisce un oggetto al quale verrà aggiunta una nuova funzionalità
  4. Decorator: definisce l’interfaccia conforme all’interfaccia del Component e mantiene l’associazione con l’oggetto Component
  5. ConcreteDecorator: implementa l’interfaccia Decorator al fine di aggiungere nuove funzionalità all’oggetto.

Possiamo schematizzare in UML con il Class Diagram:

Decorator Pattern

Decorator Pattern

Conseguenze

Tale pattern presenta i seguenti vantaggi/svantaggi:

  1. maggiore flessibilità rispetto alla eredità: permette di aggiungere funzionalità in modo molto più semplice rispetto all’ereditarietà
  2. funzionalità solo se richieste: consente di aggiungere delle funzionalità solo se occorrono realmente senza ereditare una struttura di classi che prevede un insieme di funzionalità di cui se ne utilizzeranno olo una parte. Nel caso in cui tali funzionalità sono anche a pagamento, consente di scegliere solo quelle strettamente necessarie da acquistare, coprendo esigenze di budget.
  3. aumento di micro-funzionalità: la presenza di molte classi Decorator di cui ognuna di esse aggiunge una micro funzionalità, può creare problemi in fase di comprensione o di debug del codice.

Implementazione

Un esempio noto del pattern Decorator lo troviamo nelle librerie java ed esattamente nelle classi di java.io.InputStream in cui i partecipanti sono cosi suddivisi:

  • Component: la classe astratta InputStream
  • ConcreteComponent: le classi ByteArrayInputStream, FileInputStream, ObjectInputStream, PipedInputStream, SequenceInputStream e StringBufferInputStream
  • Decorator: la classe FilterInputStream
  • ConcreteDecorator: le classi BufferedInputStream, DataInputStream, LineNumberInputStream e PushbackInputStream

L’utilizzo di questo pattern consente di poter scegliere la funzionalità di nostro interesse quando occorre leggere uno stream di dati, per esempio utilizzando BufferedInputStream è possibile bufferizzare lo stream.

Decorator Pattern: InputStream

Decorator Pattern: InputStream

Per esempio, possiamo wrappare la classe concreta FileInputStream di tipo ConcreteComponent con la classe concreta BufferedInputStream di tipo ConcreteDecorator, come nell’esempio seguente:

package patterns.decorator;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class FileDecorator {

    public static void main(String[] args) throws FileNotFoundException {
        InputStream inputStream = new BufferedInputStream(new FileInputStream("myFile"));
    }

}

Riprendiamo ed estendiamo l’esempio iniziale: pensiamo al caso in cui abbiamo l’esigenza di monitorare l’invocazione di un metodo ma non abbiamo la possibilità di modificare il codice. Utilizziamo il pattern Decorator per esigenze di debug pertanto “wrappiamo” un metodo con delle semplici istruzioni print-screen.

Rappresentiamo questa situazione nel Class Diagram:

Decorator Pattern

Decorator Pattern – Logging

Creiamo l’interfaccia Component che dichiara il metodo interessanto.

package patterns.decorator;
public interface MyComponent {
    public void operation();
}

Implementiamo il metodo dichiarato nell’interfaccia MyComponent creando la classe ConcreteComponent.

package patterns.decorator;
public class ConcreteComponent implements MyComponent {
    public void operation(){
        System.out.println("Hello World");
    }
}

Definiamo l’interfaccia MyDecorator che si occupa di ereditare il metodo interessato da MyComponent e di interporsi con le classi di decorazione concrete.

package patterns.decorator;
interface MyDecorator extends MyComponent {
}

Creiamo la classe LoggingDecorator che implementa l’interfaccia MyDecorator ed aggiunge le informazioni di debug prima e dopo l’esecuzione del metodo interessato.

package patterns.decorator;
public class LoggingDecorator implements MyDecorator {

    MyComponent myComponent = null;

    public LoggingDecorator(MyComponent myComponent){
        this.myComponent = myComponent;
    }

    public void operation() {
        System.out.println("First Logging");
        myComponent.operation();
        System.out.println("Last Logging");
    }
}

La classe Client invoca la classe concreta LoggingDecorator passando al costruttore il componente concreto, successivamente invoca il metodo operation().

package patterns.decorator;
class Client {
    public static void main(String[] args) {
        MyComponent myComponent = new LoggingDecorator(new ConcreteComponent());
        myComponent.operation();
    }
}

L’output è il seguente:

$JAVA_HOME/bin/java patterns.decorator.Cliente
First Logging
Hello World
Last Logging

Partendo dall’esempio, possiamo creare una moltitudine di classi concrete Decorator che aggiungono nuove funzionalità. Per esempio possiamo creare una classe WaitingDecorator che preveda una pausa durante l’esecuzione. Vediamo come diventa il Class Diagram a seguito dell’inseriemento di questa nuova classe.

Decorator Pattern - waiting + logging

Decorator Pattern – Logging + Waiting

package patterns.decorator;
public class WaitingDecorator implements MyDecorator {

    MyComponent myComponent = null;

    public WaitingDecorator(MyComponent myComponent){
        this.myComponent = myComponent;
    }

    public void operation() {
        try {
            System.out.println("Waiting...");
            Thread.sleep( 1000 );
        }
        catch (Exception e) {}

        myComponent.operation();
    }

}

Il Client invoca in modo annidato i Decorator tramite il loro costruttore, come segue:

package patterns.decorator;
class Client {
    public static void main(String[] args) {
        MyComponent myComponent = new LoggingDecorator(new WaitingDecorator(new ConcreteComponent()));
        myComponent.operation();
    }
}

L’output è il seguente:

$JAVA_HOME/bin/java patterns.decorator.Cliente
First Logging
Waiting...
Hello World
Last Logging
Categorie:GOF Pattern, java
  1. 24 febbraio 2012 alle 11:07 AM

    Questo post è assolutamente illuminante. Utile e molto chiaro come tutti gli altri.
    Grazie mille, continua così!

    • 24 febbraio 2012 alle 6:24 PM

      Grazie mille. Il mio obiettivo è proprio questo: sintetizzare il concetto e fare esempi semplici.

  2. Paolo
    16 aprile 2013 alle 3:02 PM

    Grazie, molto chiaro

  3. Ig89
    11 giugno 2013 alle 4:54 PM

    Molto chiaro..complimenti🙂
    Ma avrei un paio di domande.. Nel primo diagramma uml , correggimi se sbaglio(sono un novellino🙂 ) , concreteDecorator1 e concreteDecorator2 non dovrebbero essere legati con Decorator con una freccia tratteggiata?
    E un’ultima curiosità(sempre nel primo diagramma uml) Decorator, essendo una interfaccia, non implementa ma estende MyComponent, giusto?! quindi la freccia dovrebbe essere continua?

    • 13 giugno 2013 alle 2:00 AM

      Giusto, hai ragione, è stato un mio errore, ho invertito le frecce e non ci ho fatto caso. Grazie, proverò a controllare anche in altri diagrammi.
      Se ti può essere utile, puoi trovare dettagli in merito alle notazioni direttamente nella specifica UML, la superstruttura, dalla quale ho estratto questo specchietto riepilogativo (pag.143).

  4. Ig89
    13 giugno 2013 alle 2:44 PM

    persone disponibili come te ce ne sono poche !!🙂

  5. 10 luglio 2013 alle 6:03 PM

    illuminante, sei stato davvero molto chiaro!

  6. Jimmy
    1 agosto 2013 alle 2:51 AM

    Bella guida!😀

  7. Carlo
    19 febbraio 2014 alle 6:29 PM

    Mi sto trovando a dover studiare i Design Pattern e devo farti i miei complimenti! Spiegazione chiara e concisa…esempi semplici che permettono di comprendere maggiormente la parte teorica. Grazie mille per l’ottimo lavoro.

    • 21 marzo 2014 alle 5:09 PM

      In fondo il modo in cui spiego riflette il modo in cui studio: teoria essenziale e subito un esempio semplice e diretto. Solo dopo aver capito il “senso” passo ai dettagli. Ho notato che non sono l’unico a preferire questo approccio di studio. Siamo almeno in 2!😉 Grazie.

  8. 25 aprile 2014 alle 5:36 PM

    Domanda da neofita: nel caso concreto si potrebbe anche far a meno di estendere MyDecorator con MyComponent ed implementare direttamente MyComponent in LogginDecorator o sbaglio?

    • 8 maggio 2014 alle 12:32 PM

      In base all’esempio che ho fatto, la presenza di MyDecorator non è apprezzata in quanto non porta nessun valore aggiunto, pertanto la tua domanda è giusta.
      Per essere più conforme al GoF mi occorre “arricchire” MyDecorator in modo che funga da anello di collegamento tra i componenti “decoratori” e l’interfaccia dei componenti. Per fare questo ho modificato MyDecorator e LoggingDecorator, di conseguenza anche il Client.

      package patterns.decorator;
      class Client {
          public static void main(String[] args) {
              MyComponent myComponent = new LoggingDecorator(new ConcreteComponent());
              myComponent.operation();
          }
      }
      
      package patterns.decorator;
      
      public abstract class  MyDecorator implements MyComponent {
          private MyComponent myComponent;
          
          public MyDecorator(MyComponent myComponent){
              this.myComponent = myComponent;
          }
      
          @Override
          public void operation() {
              this.myComponent.operation();
          }
      
      }
      
      package patterns.decorator;
      public class LoggingDecorator extends MyDecorator {
       
          public LoggingDecorator(MyComponent myComponent){
              super(myComponent);
          }
       
          @Override
          public void operation() {
              System.out.println("First Logging");
              super.operation();
              System.out.println("Last Logging");
          }
      }
      
  9. Luca
    11 giugno 2014 alle 5:59 PM

    Anche io sto iniziando a studiare i design pattern e questi post sono i migliori.
    Complimenti per la semplicità e la facile comprensione del testo. NOn nè da tutti

  1. 15 giugno 2013 alle 2:18 AM
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: