Home > GOF Pattern, java > GoF Patterns: Composite

GoF Patterns: Composite

21 febbraio 2011

Translate in English with Google Translate
In questo articolo tratterò il pattern Composite

Motivazione
Si tratta di un pattern strutturale basato su oggetti che viene utilizzato quando si ha la necessità di realizzare una gerarchia di oggetti in cui l’oggetto contenitore può detenere oggetti elementari e/o oggetti contenitori. L’obiettivo è di permettere al Client che deve navigare la gerarchia, di comportarsi sempre nello stesso modo sia verso gli oggetti elementari e sia verso gli oggetti contenitori.

Partecipanti e Struttura

Questo pattern è composto dai seguenti partecipanti:

  1. Client: colui che effettua l’invocazione all’operazione di interesse
  2. Component: definisce l’interfaccia degli oggetti della composizione.
  3. Leaf: rappresenta l’oggetto foglia della composizione. Non ha figli. Definisce il comportamento “primitivo” dell’oggetto della composizione
  4. Composite: definisce il comportamento degli oggetti usati come contenitori ed detiene il riferimento ai componenti “figli”.

In UML, usando il Class Diagram, possiamo schematizzare le relazioni in questo modo:

Composite - Struttura

Composite – Struttura

Nella programmazione java abbiamo esempi pratici di questa gerachia quando utilizziamo i packages che sono delle cartelle che contengono classi o packages annidati. In UML è possibile rappresentare la gerarchia attraverso il Package Diagram che ci consente di esprimere il grado di annidamento e le relazioni esisteni tra packages.
Nel nostro caso evidenziamo solo l’annidamento ed immaginando di avere una struttura di LEAF e COMPOSITE gerarchicamente annidati possiamo rappresentarlo in questo modo:

Package Diagram

Package Diagram

La rappresentazione della gerarchia può essere vista come una serie di relazioni che legano genitori e figli a cascata:

Conseguenze

Tale pattern presenta i seguenti vantaggi/svantaggi:

  1. Definisce la gerarchia: Gli oggetti della gerarchia possono essere composti da oggetti semplici e/o da oggetti contenitori che a loro volta sono composti ricorsivamente da altri oggetti semplici e/o da oggetti contenitori .
  2. Semplifica il client: il Client tratta gli oggetti semplici e gli oggetti contenitori nello stesso modo. Questo semplifica il suo lavoro il quale astrae dalla specifica implementazione.
  3. Semplifica la modifica dell’albero gerarchico: l’alberatura è facilmente modificabile aggiungendo/rimuovendo foglie e contenitori.

Implementazione

Come esempio pensiamo al FileSystem che presenta una struttura ad albero e che può essere composto da elementi semplici ( i files ) e da contenitori ( le cartelle ). L’obiettivo del nostro esercizio è quello di permettere al Client di accedere e navigare il File System senza conoscere la natura degli elementi che lo compongono in modo da consentire al Client di trattare tutti gli elementi nello stesso modo.
Per fare questo il Client userà la stessa interfaccia per l’accesso mentre l’implementazione nasconderà la gestione degli oggetti a seconda della loro reale natura.
Per fare questo schematizziamo la struttura delle classi usando il Class Diagram seguente:

Composite Esercizio

Composite Esercizio

Creiamo l’interfaccia di interrogazione per l’accesso a files e cartelle.
Creiamo i metodi add/remove per aggiungere/rimuovere files/cartelle ed il metodo print per visualizzare il suo nome.

package patterns.composite;

public interface MyFileSystem {

    public void add(MyFileSystem myFileSystem);

    public void remove(MyFileSystem myFileSystem);

    public void print();
}

Implementiamo la classe che gestisce i Files. In questo caso i metodo add/remove non sono implementabili.

package patterns.composite;

public class MyFile implements MyFileSystem {

    private String myFileName = null;

    public MyFile(String myFileName) {
        this.myFileName = myFileName;
    }

    @Override
    public void print() {
        System.out.println(myFileName);
    }

    @Override
    public void add(MyFileSystem myFileNameSystem) {
        System.out.println("Impossible to add!");
    }

    @Override
    public void remove(MyFileSystem myFileNameSystem) {
        System.out.println("Impossible to remove!");
    }
}

Implementiamo la classe che gestisce le Cartelle. In questo caso i metodo add/remove aggiungono/rimuovono nuovi files/cartelle alla cartella corrente.

package patterns.composite;

import java.util.ArrayList;

public class MyFolder implements MyFileSystem {

    private String myFolderName;
    private ArrayList<MyFileSystem> folder;

    public MyFolder(String myFolderName) {
        this.myFolderName = myFolderName;
        folder = new ArrayList<MyFileSystem>();
    }

    @Override
    public void print() {
        System.out.println(myFolderName);
        for (int i = 0; i < folder.size(); i++) {
            folder.get(i).print();
        }
    }

    @Override
    public void add(MyFileSystem myFileSystem) {
        folder.add(myFileSystem);
    }

    @Override
    public void remove(MyFileSystem myFileSystem) {
        folder.remove(myFileSystem);
    }
}

Il metodo print viene invocato su tutti gli oggetti dell’albero, siano essi files o cartelle. Il comportamento sarà diverso nei 2 casi. Nella classe MyFile si limita a stampare il nome del file. Nella classe MyFolder , oltre a stampare il nome della cartella, presenta un loop utilizzato per invocare files/cartelle per consentire la ricorsione su tutta l’alberatura del Files System.

...
    folder.get(i).print();
...

La classe Client crea l’alberatura del File System e poi visualizza il suo contenuto.

package patterns.composite;

public class Client {

    public static void main(String[] args) {
        MyFileSystem f2 = new MyFile("F2");
        MyFileSystem f3 = new MyFile("F3");
        MyFileSystem c2 = new MyFolder("C2");
        c2.add(f2);
        c2.add(f3);

        MyFileSystem f1 = new MyFile("F1");
        MyFileSystem c1 = new MyFolder("C1");
        c1.add(f1);
        c1.add(c2);

        c1.print();
    }
}

L’output del Client è il seguente:

$JAVA_HOME/bin/java patterns.composite.Client
C1
F1
C2
F2
F3

Graficamente l’albero del File System può essere rappresentato in questo modo:

Esempio Files System

Esempio Files System

Categorie:GOF Pattern, java
  1. Anonimo
    5 maggio 2012 alle 2:10 PM

    Ottima spiegazione!

  2. laura
    15 giugno 2012 alle 2:10 PM

    Complimenti veramente chiaro Grazie!!!!

  3. Ig89
    27 marzo 2013 alle 11:59 AM

    Molto chiaro🙂 Avrei una domanda , come potrei utilizzare il pattern composite per un grafo orientato e pesato che ha un nodo iniziale(cioè non ha archi entranti) , dei nodi intermedi ed un nodo finale (cioè non ha archi uscenti)

    • 1 aprile 2013 alle 4:22 PM

      Un grafo è composto da nodi ed archi G=(V,E) e può essere strutturato sia con la composizione che con l’ereditarietà.
      Con la composizione abbiamo un Grafo composto da una lista di Nodi (V) ed una lista di adiacenza per definire gli Archi (E).
      Con l’ereditarietà invece abbiamo Nodo e Arco che ereditano da Grafo.
      Tra le due soluzioni, la prima presenta maggiori flessibilità, soprattutto per affrontare problemi complessi dei grafi (vedi il progetto JGrapht) ma la seconda soluzione è più snella per casi più semplici.
      Le proprietà degli archi relativi all’orientamento e peso possono essere associati utilizzando il decorator pattern, mentre la navigazione del grafo può essere effettuata con la tecnica della ricorsione oppure utilizzando il pattern composite.
      Ho fatto un esempio usando il pattern composite e decorator utilizzando l’ereditarietà.

      package patterns.composite.alberoRadicatoOrientatoPesato;
      public abstract class Grafo {
          public abstract void add(Grafo grafo);
          public abstract void esplora();
      }
      
      package patterns.composite.alberoRadicatoOrientatoPesato;
      import java.util.Vector;
      public class Nodo extends Grafo {
          private Vector listaNodi;
          private String nomeNodo;
          public Nodo(String nomeNodo) {
              this.nomeNodo = nomeNodo;
              listaNodi = new Vector();
          }
          @Override
          public void add(Grafo grafo) {
              listaNodi.add(grafo);
          }
          @Override
          public void esplora() {
              System.out.println("nome nodo: " + nomeNodo);
              for (int i = 0; i < listaNodi.size(); i++) {
                  ((Grafo) listaNodi.get(i)).esplora();
              }
          }
      }
      
      package patterns.composite.alberoRadicatoOrientatoPesato;
      public class Arco extends Grafo {
          private int peso;
          private Grafo grafo;
          public Arco(int peso, Grafo grafo) {
              this.peso = peso;
              this.grafo = grafo;
          }
          @Override
          public void add(Grafo grafo) {
              throw new UnsupportedOperationException("Not supported yet.");
          }
          @Override
          public void esplora() {
              System.out.println("peso arco: " + peso );
              grafo.esplora();
          }
      }
      
      package patterns.composite.alberoRadicatoOrientatoPesato;
      public class Client {
          public static void main(String[] args) {
              Grafo myGrafo = creaGrafo();
              myGrafo.esplora();
          }
          public static Grafo creaGrafo() {
              Grafo nodoIniziale = new Nodo("nodoIniziale");
              Grafo nodo1 = new Nodo("nodo1");
              Grafo nodo2 = new Nodo("nodo2");
              Grafo nodoFinale = new Nodo("nodoFinale");
              nodoIniziale.add(new Arco( 2, nodo1) );
              nodoIniziale.add(new Arco( 3, nodo2));
              nodo1.add(new Arco( 4, nodoFinale));
              nodo2.add(new Arco( 5, nodoFinale));
              return nodoIniziale;
          }
      }
      
      $JAVA_HOME/bin/java patterns.composite.alberoRadicatoOrientatoPesato.Client
      nome nodo: nodoIniziale
      peso arco: 2
      nome nodo: nodo1
      peso arco: 4
      nome nodo: nodoFinale
      peso arco: 3
      nome nodo: nodo2
      peso arco: 5
      nome nodo: nodoFinale
      
      • ig89
        2 aprile 2013 alle 12:26 AM

        Sei stato molto utile e soprattutto molto gentile .. Grazie🙂

  4. Ig89
    2 aprile 2013 alle 1:25 PM

    Ti ringrazio per la disponibilità mi hai risolto un grosso dubbio😀

  5. 13 aprile 2013 alle 12:02 PM

    Ciao, complimenti per le ottime lezioni sui pattern, veramente molto utili.
    Sto rivedendo il codice di tutti gli esempi e ho avuto problemi con quello riportato per questo pattern; in buona sostanza, utilizzando Netbeans 7.3, Java 7, ho qualche problema nella classe Client perché non è possibile dichiarare le istanze di MyFileSystem e contestualmente assegnare i valori, che sono di tipo String.
    Ho superato la problematica riscrivendo nelle classi MyFile e MyFolder il costruttore corrispondente sostituendolo con un metodo myFileSystemName (che ho incluso anche nell’interfaccia MyFileSystem), dichiarandolo di tipo String e riscrivendo la classe Client in questo modo:

    public static void main(String[] args){
        MyFileSystem f2=new MyFile();
        MyFileSystem f3=new MyFile();
        MyFileSystem c2=new MyFolder();
        f2.myFileSystemName("F2");
        f3.myFileSystemName("F3");
        c2.myFileSystemName("C2");
        c2.add(f2);
        c2.add(f3);
        MyFileSystem f1=new MyFile();
        MyFileSystem c1=new MyFolder();
        f1.myFileSystemName("F1");
        c1.myFileSystemName("C1");
        c1.add(f1);
        c1.add(c2);
        c1.print();
    }
    

    Lo sottopongo alla tua valutazione per verificare come mai ho dovuto fare questa modifica.
    Grazie per la disponibilità e complimenti ancora per la tua competenza.

    • 13 aprile 2013 alle 12:52 PM

      Ho rivisto il codice; si trattava di un problema del compilatore. Alla fine ha compilato tutto. Ho fatto comunque un po d’esperienza. Grazie.

      • 13 aprile 2013 alle 5:17 PM

        Mi fa piacere che hai risolto, ma resto curioso dell’errore che hai ricevuto.
        Da quanto mi hai spiegato si trattava di un errore a compile-time riferito al costruttore MyFileSystem, probabilmente dovuto al carattere di doppio apice convertito in modo errato, in questo caso avresti dovuto ricevere l’errore: “illegal character: \8220” e per correggerlo avresti dovuto ridigitare il carattere doppio apice in netbeans per sostituirlo con quello corretto.
        In ogni caso sono contento che i miei articoli ti siano utili e grazie mille per i complimenti.🙂

      • 13 aprile 2013 alle 7:42 PM

        Non ho capito come è successo; credo qualche errore di digitazione, come dici giustamente…grazie ancora a te🙂

  6. Franzi90
    11 agosto 2013 alle 5:31 PM

    Ciao scusa. Potresti spiegarmi con maggiore accuratezza cosa significa che aggiungi le caratteristiche dell’orientamento e del peso degli archi tramite il pattern decorate?
    Sbaglio o il peso è semplicemente una variabile di istanza il peso in arco e l’orientamento non è minimamente preso in considerazione?

  7. Franzi90
    11 agosto 2013 alle 6:24 PM

    Scusa se continuo ancora, mi sa che ho capito. L’orientamento dei nodi è dovuto intrinsecamente alla listanodi di ogni nodo che rappresenta la lista dei nodi ad esso adiacente?

    • 21 agosto 2013 alle 2:06 AM

      Esatto, la listaNodi rappresenta una semplice lista di adiacenza in cui sono elencati i nodi confinanti.
      Sarebbe stato possibile utilizzare anche una matrice di adiacenza o di incidenza ma in presenza di pochi archi la lista di adiacenza è preferibile.

  8. Pippo
    5 dicembre 2013 alle 1:26 PM

    ciao, veramente chiaro nelle spiegazioni dei vari design pattern.
    Ho solo un piccolo dubbio, nella classe MyFolder quando dichiari il vettore eclipse mi da una warning nella riga 23 del codice in questione.
    Ho risolto il problema dichiarando il vettore come MyFileSystem:
    -Vector folder;
    -e nel costruttore ho modificato la riga 12 come folder = new Vector ();
    -ho tolto anche il cast nella funzione print()

    • 5 dicembre 2013 alle 5:19 PM

      Probabilmente i warning che hai ricevuto si riferiscono al fatto che ho utilizzato la classe Vector etichettata come “obsoleta” e non ho utilizzato i Generic pertanto il tuo dubbio è corretto. Per ovviare a questi “warning” ho modificato il codice usando la classe ArrayList ed usando i Generic. Ho colto l’occasione per aggiornare anche i diagrammi UML. Grazie x le tue indicazioni. Ciao.

  9. 8 maggio 2014 alle 9:34 AM

    Buongiorno, tanto per capire, non dovrebbe esserci nel Class Diagram dell’esempio, l’aggregazione tra MyFolder e MyFileSystem come è presente nel pattern originale?

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: