Este útil patrón permite crear y manejar estructuras de objetos en forma de árbol, en las que un objeto puede contener a otro(s).
En este punto cabe aclarar que las estructuras de este tipo se componen de nodos (un objeto que a su vez contiene otros objetos) y Hojas (objetos que no contienen otros), y que ambos comparten una misma Interface que define métodos que deben implementar.
Para ilustrar cómo funciona este patrón de diseño veamos a continuación un programa de ejemplo en el que gestionamos archivos y carpetas.
Main.java (Cliente según el diagrama anterior):
package estructurales.composite.composite01;
public class Main
{
public static void main(String[] args)
{
// Crear la carpeta principal e insertar archivos
Carpeta c1 = new Carpeta("CARPETA_1");
c1.insertarNodo( new Archivo("Archivo1.txt") );
c1.insertarNodo( new Archivo("Archivo2.txt") );
c1.insertarNodo( new Archivo("Archivo3.txt") );
// Crear una subcarpeta e insertar archivos
Carpeta c2 = new Carpeta("CARPETA_2");
c2.insertarNodo( new Archivo("Archivo4.txt") );
c2.insertarNodo( new Archivo("Archivo5.txt") );
// Insertar la subcarpeta dentro de la principal
c1.insertarNodo( c2 );
// Insertar otro archivo dentro de la carpeta principal
c1.insertarNodo( new Archivo("Archivo6.txt") );
c1.mostrar();
System.out.println("--------------------");
// Eliminamos la subcarpeta (junto con su contenido)
c1.eliminarNodo( c2 );
c1.mostrar();
}
}
Nodo.java: (Componente según el diagrama anterior)
package estructurales.composite.composite01;
public abstract class Nodo
{
public static final int ARCHIVO = 1;
public static final int CARPETA = 2;
protected String nombre = "";
protected int tipoNodo;
// ----------------------------
public String getNombre()
{
return this.nombre;
}
// ----------------------------
public void setNombre( String nombre )
{
this.nombre = nombre;
}
// ----------------------------
public int getTipoNodo()
{
return this.tipoNodo;
}
// ----------------------------
public void setTipoNodo( int tipoNodo )
{
this.tipoNodo = tipoNodo;
}
// ----------------------------
// Método a implementar por las clases que hereden
public abstract void mostrar();
}
Archivo.java: (Hoja según el diagrama anterior)
package estructurales.composite.composite01;
public class Archivo extends Nodo
{
public Archivo( String nombre )
{
this.setTipoNodo( Nodo.ARCHIVO );
this.setNombre( nombre );
}
// ----------------------------
@Override
public void mostrar()
{
System.out.println( "Archivo: [" + this.getNombre() + "]" );
}
}
Carpeta.java: (Compuesto según el diagrama anterior)
package estructurales.composite.composite01;
import java.util.ArrayList;
import java.util.List;
public class Carpeta extends Nodo
{
List<Nodo> nodos = new ArrayList<Nodo>();
// ----------------------------
public Carpeta( String nombre )
{
this.setTipoNodo( Nodo.CARPETA );
this.setNombre( nombre );
}
// ----------------------------
public void insertarNodo( Nodo nodo )
{
nodos.add( nodo );
}
// ----------------------------
public void eliminarNodo( Nodo nodo )
{
nodos.remove( nodo );
}
// ----------------------------
public List<Nodo> getNodos()
{
return nodos;
}
// ----------------------------
public Nodo getNodo( int posicion )
{
return nodos.get( posicion );
}
// ----------------------------
@Override
public void mostrar()
{
System.out.println( "Listando carpeta [" + this.getNombre() + "]" );
for (Nodo nodo : nodos)
{
nodo.mostrar();
}
}
}
Al ejecutarlo obtendríamos como resultado:
EXPLICACIÓN:
- Los archivos son hojas (no contienen otros elementos) y las carpetas serían nodos (pueden contener archivos así como otras carpetas).
- Las clases Archivo y Carpeta heredan de la clase abstracta Nodo, que implementa los métodos comunes y define el método mostrar() que deberán implementar.
- Dicho método en la clase Archivo abre el archivo para mostrar su contenido, y en la clase Carpeta muestra su contenido de forma recursiva (si contiene otras carpetas mostrará también lo que contienen).
- Al ejecutar el programa se crea una carpeta principal y se le agregarán objetos de tipo Archivo así como otra subcarpeta que creamos posteriormente, y después mostramos su contenido.
- A continuación eliminamos la subcarpeta y volvemos a mostrar el contenido de la principal
Veamos ahora otro ejemplo en el que se recorre cada elemento del árbol y se muestra el nombre del nodo padre de cada uno de ellos.
package estructurales.composite.composite02;
public class Main
{
public static void main(String[] args)
{
// Crear la carpeta principal e insertar archivos
Carpeta c1 = new Carpeta( "CARPETA_1", null );
c1.insertarNodo( new Archivo("Archivo1.txt", c1) );
c1.insertarNodo( new Archivo("Archivo2.txt", c1) );
c1.insertarNodo( new Archivo("Archivo3.txt", c1) );
// Crear una subcarpeta e insertar archivos
Carpeta c2 = new Carpeta("CARPETA_2", c1);
c2.insertarNodo( new Archivo("Archivo4.txt", c2) );
c2.insertarNodo( new Archivo("Archivo5.txt", c2) );
// Insertar la subcarpeta dentro de la principal
c1.insertarNodo( c2 );
// Insertar otro archivo dentro de la carpeta principal
c1.insertarNodo( new Archivo("Archivo6.txt", c1) );
c1.recorrerNodos();
}
}
Nodo.java: (Componente según el diagrama anterior)
package estructurales.composite.composite02;
public abstract class Nodo
{
public static final int ARCHIVO = 1;
public static final int CARPETA = 2;
protected String nombre = "";
protected int tipoNodo;
protected Nodo nodoPadre;
// ----------------------------
public String getNombre()
{
return this.nombre;
}
// ----------------------------
public void setNombre( String nombre )
{
this.nombre = nombre;
}
// ----------------------------
public int getTipoNodo()
{
return this.tipoNodo;
}
// ----------------------------
public void setTipoNodo( int tipoNodo )
{
this.tipoNodo = tipoNodo;
}
// ----------------------------
public Nodo getNodoPadre()
{
return this.nodoPadre;
}
// ----------------------------
public void setNodoPadre( Nodo nodoPadre )
{
this.nodoPadre = nodoPadre;
}
// ----------------------------
// Método a implementar por las clases que hereden
public abstract void mostrar();
}
Archivo.java: (Hoja según el diagrama anterior)
package estructurales.composite.composite02;
public class Archivo extends Nodo
{
public Archivo( String nombre, Nodo nodoPadre )
{
this.setTipoNodo( Nodo.ARCHIVO );
this.setNombre( nombre );
this.setNodoPadre( nodoPadre );
}
// ----------------------------
@Override
public void mostrar()
{
System.out.println( "Mostrando el contenido del archivo [" + this.getNombre() + "]" );
}
}
Carpeta.java: (Compuesto según el diagrama anterior)
package estructurales.composite.composite02;
import java.util.ArrayList;
import java.util.List;
public class Carpeta extends Nodo
{
List<Nodo> nodos = new ArrayList<Nodo>();
// ----------------------------
public Carpeta( String nombre, Nodo nodoPadre )
{
this.setTipoNodo( Nodo.CARPETA );
this.setNombre( nombre );
this.setNodoPadre( nodoPadre );
}
// ----------------------------
public void insertarNodo( Nodo nodo )
{
nodos.add( nodo );
}
// ----------------------------
public void eliminarNodo( Nodo nodo )
{
nodos.remove( nodo );
}
// ----------------------------
public List<Nodo> getNodos()
{
return nodos;
}
// ----------------------------
public Nodo getNodo( int posicion )
{
return nodos.get( posicion );
}
// ----------------------------
// Recorre cada nodo del árbol mostrando su nombre y el de su nodo padre
public void mostrarPadres()
{
for (Nodo nodo : nodos)
{
System.out.println( "El nodo padre de [" + nodo.getNombre() + "] es [" + nodo.getNodoPadre().getNombre() + "]" );
// Si se trata de otra carpeta, mostrar también su contenido
if( nodo.getTipoNodo() == Nodo.CARPETA )
{
((Carpeta) nodo).mostrarPadres();
}
}
}
// ----------------------------
@Override
public void mostrar()
{
System.out.println( "Listando carpeta [" + this.getNombre() + "]" );
for (Nodo nodo : nodos)
{
nodo.mostrar();
}
}
}
Al ejecutarlo obtendríamos como resultado:
EXPLICACIÓN:
- En la clase abstracta Nodo se ha agregado la propiedad nodoPadre y los métodos necesarios para que los objetos de las clases que hereden de ésta guarden una referencia a su nodo padre en el momento de su instanciación.
- El método mostrarPadres() de la clase Carpeta es el encargado de recorrer el árbol para ir mostrando el nombre de cada elemento así como el de su nodo padre.

En en el diagrama UML del libro de GoF todos los métodos de la clase Compuesto se encuentran también en la clase abstracta de la que hereda, pero hemos dejado tan sólo operacion() dado que Hoja en principio no contiene otros hijos.