Composite

Disambiguazione – Se stai cercando le piante della famiglia delle Compositae della classe dei dicotiledoni, vedi Asteraceae.

Nella programmazione ad oggetti, il Composite è uno dei pattern fondamentali, definiti originariamente dalla Gang of Four.

Questo pattern permette di trattare un gruppo di oggetti come se fossero l'istanza di un oggetto singolo. Il design pattern Composite organizza gli oggetti in una struttura ad albero, nella quale i nodi sono delle composite e le foglie sono oggetti semplici.

È utilizzato per dare la possibilità ai client di manipolare oggetti singoli e composizioni in modo uniforme.

Struttura di una composite

[modifica | modifica wikitesto]
Struttura del pattern Composite
  • Client: manipola gli oggetti attraverso l'interfaccia Component.
  • Component: dichiara l'interfaccia per gli oggetti nella composizione, per l'accesso e la manipolazione di questi, imposta un comportamento di default per l'interfaccia comune a tutte le classi e può definire un'interfaccia per l'accesso al padre del componente e la implementa se è appropriata.
  • Composite: definisce il comportamento per i componenti aventi figli, salva i figli e implementa le operazioni ad essi connesse nell'interfaccia Component.
  • Leaf: definisce il comportamento degli oggetti primitivi, cioè delle foglie.

Quando i programmatori trattano dati strutturati ad albero devono spesso discriminare se stanno visitando un nodo o una foglia. Questa differenza è una possibile fonte di complessità per il codice e, se non trattata a dovere, rende il programma facilmente soggetto a errori. La soluzione è adottare un'interfaccia che permetta di trattare oggetti complessi e primitivi in modo uniforme. Nella programmazione orientata agli oggetti un Composite è un oggetto (per esempio una figura geometrica) progettato per composizione di uno o più oggetti simili (sempre per rimanere sullo stesso esempio: una figura geometrica come un trapezio può essere vista a sua volta come formata da due triangoli rettangoli e un rettangolo) che offrono tutti le medesime funzionalità. Questa caratteristica è conosciuta anche come relazione "has-a" tra gli oggetti.

Il concetto fondamentale è che il programmatore manipola ogni oggetto dell'insieme dato nello stesso modo: sia esso un "raggruppamento" o un oggetto singolo. Le operazioni che possono essere effettuate sul Composite hanno spesso un denominatore comune, per esempio: se il programmatore deve visualizzare a schermo un insieme di figure potrebbe essergli utile definirne il ridimensionamento in modo tale da avere lo stesso effetto (in senso lato) del ridimensionamento di una singola figura.

Quando usarlo

[modifica | modifica wikitesto]

Il Composite può essere usato quando i client dovrebbero ignorare la differenza tra oggetti composti e oggetti singoli. Se durante lo sviluppo i programmatori scoprono che stanno usando più oggetti nello stesso modo, e spesso il codice per gestirli è molto simile, il Composite rappresenta una buona scelta di rifattorizzazione: in questa situazione, è meno complesso trattare oggetti primitivi e composti in modo omogeneo.

Funzionamento

[modifica | modifica wikitesto]

Attraverso l'interfaccia Component, il Client interagisce con gli oggetti della composite. Se l'oggetto desiderato è una Leaf, la richiesta è processata direttamente; altrimenti, se è una Composite, viene rimandata ai figli cercando di svolgere le operazioni prima e dopo del rimando.

In questo modo, si semplifica il Client, si creano delle gerarchie di classi, si semplifica l'aggiunta di nuovi componenti, anche se il design diventa troppo generale.

Il seguente esempio, scritto in Java, implementa una classe grafica che può essere o un'ellisse o una composizione di vari ellissi (o di qualsiasi altra classe che implementa Graphic, nel seguente esempio abbiamo solo Ellisse). Ogni figura può essere stampata. In forma algebrica,

Graphic = ellipse | GraphicList GraphicList = empty | Graphic GraphicList 

Può essere estesa fino ad implementare altre forme (rettangolo, ecc.) e metodi (traslare, ecc.).

List = empty_list | atom List | List List 
Esempio con Java
import java.util.List; import java.util.ArrayList;  interface Graphic {      //Stampa il grafico.     public void print();  }  class CompositeGraphic implements Graphic {      //Collezione di grafici figli.     private List<Graphic> mChildGraphics = new ArrayList<Graphic>();      //Stampa il grafico.     public void print() {         for (Graphic graphic : mChildGraphics) {             graphic.print();         }     }      //Aggiunge il grafico alla composizione.     public void add(Graphic graphic) {         mChildGraphics.add(graphic);     }      //Rimuove il grafico dalla composizione.     public void remove(Graphic graphic) {         mChildGraphics.remove(graphic);     }  }  class Ellisse implements Graphic {      //Stampa il grafico.     public void print() {         System.out.println("Ellisse");     }  }  public class Program {      public static void main(String[] args) {         //Inizializza tre ellissi         Ellisse ellisse1 = new Ellisse();         Ellisse ellisse2 = new Ellisse();         Ellisse ellisse3 = new Ellisse();         Ellisse ellisse4 = new Ellisse();          //Inizializza tre grafici composti         CompositeGraphic graphic = new CompositeGraphic();         CompositeGraphic graphic1 = new CompositeGraphic();         CompositeGraphic graphic2 = new CompositeGraphic();          //Compone i grafici         graphic1.add(ellisse1);         graphic1.add(ellisse2);         graphic1.add(ellisse3);          graphic2.add(ellisse4);          graphic.add(graphic1);         graphic.add(graphic2);          //Stampa i grafici completi (quattro volte la stringa "Ellisse").         graphic.print();     } } 
Esempio con C++
#include <iostream> #include <vector>  using namespace std;  class Component { public: virtual void traverse() = 0; };  class Primitive : public Component {    int value; public:    Primitive( int val ) { value = val; }    void traverse()      { cout << value << "  "; } };  class Composite : public Component {    vector<Component*> children;    int                value; public:    Composite( int val )     { value = val; }    void add( Component* c ) { children.push_back( c ); }    void traverse() {       cout << value << "  ";       for (int i=0; i < children.size(); i++)           children[i]->traverse(); }  };  class Row : public Composite { public:     // Due diversi tipi di classi    Row( int val ) : Composite( val ) { }   // "container".  La maggior parte del    void traverse() {                       // codice è nella classe base Composite       cout << "Row";                              Composite::traverse(); }  };  class Column : public Composite { public:    Column( int val ) : Composite( val ) { }    void traverse() {       cout << "Col";       Composite::traverse(); }  };  int main() {        // In questo main creeremo il seguente albero        // Row1       //   |       //   +-- Col2       //   |     |       //   |     +-- 7       //   +-- Col3       //   |     |       //   |     +-- Row4       //   |     |     |       //   |     |     +-- 9       //   |     +-- Row5       //   |     |     |       //   |     |     +-- 10       //   |     +-- 8       //   +-- 6         // I nodi intermedi       Row    Row1( 1 );       Column Col2( 2 );       Column Col3( 3 );       Row    Row4( 4 );       Row    Row5( 5 );       // Le foglie       Primitive P6(6);       Primitive P7(7);       Primitive P8(8);       Primitive P9(9);       Primitive P10(10);        // Struttura dell'albero       Row1.add( &Col2 );       Row1.add( &Col3  );       Col3.add( &Row4 );       Col3.add( &Row5  );        // Le foglie       Row1.add(  &P6 );       Col2.add( &P7 );       Col3.add(  &P8 );       Row4.add( &P9 );       Row5.add(  &P10 );        // Grazie al pettern attraversiamo l'intero albero       Row1.traverse();       cout << endl;        // L'output è:       // Row1  Col2  7  Col3  Row4  9  Row5  10  8  6        return 0; } 

Voci correlate

[modifica | modifica wikitesto]

Altri progetti

[modifica | modifica wikitesto]