Composite (patrón de diseño)

El patrón Composite sirve para construir objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol.

Esto simplifica el tratamiento de los objetos creados, ya que al poseer todos ellos una interfaz común, se tratan todos de la misma manera. Dependiendo de la implementación, pueden aplicarse procedimientos al total o una de las partes de la estructura compuesta como si de un nodo final se tratara, aunque dicha parte esté compuesta a su vez de muchas otras. Un claro ejemplo de uso extendido de este patrón se da en los entornos de programación 2D para aplicaciones gráficas. Un videojuego puede contener diferentes capas "layers" de sprites (como una capa de enemigos) pudiéndose invocar un método que actúe sobre toda esta capa de sprites a la vez (por ejemplo, para ocultarlos, darles un filtro de color etc.).

Es uno de los veintitrés patrones de diseño GoF conocidos que describen cómo resolver problemas recurrentes de diseño para diseñar software orientado a objetos.

Problema que soluciona editar

Imaginemos que necesitamos crear una serie de clases para guardar información acerca de una serie de figuras que serán círculos, cuadrados y triángulos. Además necesitamos poder tratar también grupos de imágenes porque nuestro programa permite seleccionar varias de estas figuras a la vez para moverlas por la pantalla.

En principio tenemos las clases Círculo, Cuadrado y Triángulo, que heredarán de una clase padre que podríamos llamar Figura e implementarán todas la operación pintar(). En cuanto a los grupos de Figuras podríamos caer en la tentación de crear una clase particular separada de las anteriores llamada GrupoDeImágenes, también con un método pintar().

Problema.

Esta idea de separar en clases privadas componentes (figuras) y contenedores (grupos) tiene el problema de que, para cada uno de las dos clases, el método pintar() tendrá una implementación diferente, aumentando la complejidad del sistema.

Implementación editar

El patrón Composite da una solución elegante a este problema, de la que además resulta en una implementación más sencilla.

A la clase Figura la llamaríamos Gráfico y de ella extenderían tanto Círculo, Cuadrado y Triángulo, como GrupoDeImágenes. Además, esta última tendría una relación todo-parte de multiplicidad * con Gráfico: un GrupoDeImágenes contendría varios Gráficos, ya fuesen éstos Cuadrados, Triángulos, u otras clases GrupoDeImágenes.

Así, es posible definir a un grupo de imágenes recursivamente. Por ejemplo, un objeto cuya clase es GrupoDeImágenes podría contener un Cuadrado, un Triángulo y otro GrupoDeImágenes, este grupo de imágenes podría contener un Círculo y un Cuadrado. Posteriormente, a este último grupo se le podría añadir otro GrupoDeImágenes, generando una estructura de composición recursiva en árbol, por medio de muy poca codificación y un diagrama sencillo y claro.

Diagrama editar

 

Ejemplos de utilización editar

En Java: las clases java.awt.Component (Componente), java.awt.Container (Contenedor), java.awt.Panel (Contenedor concreto), java.awt.Button (Botón)

Código en Kotlin editar

import java.util.ArrayList

abstract class Componente(protected var nombre: String) {
    abstract fun agregar(c: Componente)
    abstract fun eliminar(c: Componente)
    abstract fun mostrar(profundidad: Int)
}

class Compuesto(name: String) : Componente(name) {
    private val hijo = ArrayList<Componente>()


    override fun agregar(componente: Componente) {
        hijo.add(componente)
    }

    override fun eliminar(componente: Componente) {
        hijo.remove(componente)
    }

    override fun mostrar(profundidad: Int) {
        println("$nombre nivel: $profundidad")
        for (i in hijo.indices) hijo[i].mostrar(profundidad + 1)
    }
}

internal class Hoja(nombre: String) : Componente(nombre) {
    override fun agregar(c: Componente) {
        println("no se puede agregar la hoja")
    }

    override fun eliminar(c: Componente) {
        println("no se puede quitar la hoja")
    }

    override fun mostrar(depth: Int) {
        println("-$nombre")
    }
}

object Client {
    @JvmStatic
    fun main(args: Array<String>) {
        val raiz = Compuesto("root")
        raiz.agregar(Hoja("hoja A"))
        raiz.agregar(Hoja("hoja B"))
        val comp = Compuesto("compuesto X")
        comp.agregar(Hoja("hoja XA"))
        comp.agregar(Hoja("hoja XB"))
        raiz.agregar(comp)
        raiz.agregar(Hoja("hoja C"))
        val l = Hoja("hoja D")
        raiz.agregar(l)
        raiz.eliminar(l)
        raiz.mostrar(1)
    }
}

Código en C++ editar

#include <iostream>
#include <vector>

using namespace std;

class Component
{
protected:
	string name;
public:
	Component();
	Component(string n);
	virtual void add(Component*) {}
	virtual void remove(Component*) {}
	virtual void show(short) {}
};

Component::Component() {}
Component::Component(string n) : name(n) {}

class Composite : public Component
{
private:
	vector<Component*> list;
public:
	Composite(string);
	void add(Component*);
	void remove(Component*);
	void show(short);
};

Composite::Composite(string n) { name = n; }
void Composite::add(Component* component)
{
	list.push_back(component);
}
void Composite::remove(Component* component)
{
	list.erase(std::remove(list.begin(), list.end(), component), list.end());
}
void Composite::show(short depth)
{
    cout << name << " nivel: " << depth << endl;
    for(vector<Component*>::const_iterator iter = list.begin(); iter != list.end(); ++iter)
    {
        if(*iter != 0)
        {
            (*iter)->show(depth + 1);
        }
    }
}

class Leaf : public Component
{
public:
	Leaf (string);
	void add(Component*) {}
	void remove(Component*) {}
	void show(short);
};

Leaf::Leaf(string n) { name = n; }
void Leaf::show(short depth)
{
	cout << '-' << name << "    (" << depth << ')' << endl;
}

int main()
{
	Composite* root = new Composite("raiz");
	root->add(new Leaf("hoja A"));
	root->add(new Leaf("hoja B"));
	
	Composite* comp = new Composite("rama");
	comp->add(new Leaf("hoja A'"));
	comp->add(new Leaf("hoja B'"));

	root->add(comp);
	root->add(new Leaf("hoja C"));
	Leaf* h = new Leaf("hoja D");
	root->add(h);
	root->remove(h);
	root->show(1);

	delete h;
	delete comp;
	delete root;
	return 0;
}

Código en Java editar

import java.util.*;

public abstract class Componente
{
	protected String nombre;
	public Componente (String nombre)
	{
		this.nombre = nombre;
	}
	abstract public void agregar(Componente c);
	abstract public void eliminar(Componente c);
	abstract public void mostrar(int profundidad);
}
class Compuesto extends Componente
{
	private ArrayList<Componente> hijo = new ArrayList<Componente>();
	public Compuesto (String name)
	{
		super(name);
	}
	@Override
	public void agregar(Componente componente)
	{
		hijo.add(componente);
	}
	@Override
	public void eliminar(Componente componente)
	{
		hijo.remove(componente);
	}
	@Override
	public void mostrar(int profundidad)
	{
		System.out.println(nombre + " nivel: " + profundidad);
		for (int i = 0; i < hijo.size(); i++)
			hijo.get(i).mostrar(profundidad + 1);
	}
}
class Hoja extends Componente
{
	public Hoja (String nombre)
	{
		super(nombre);
	}
	public void agregar(Componente c)
	{
		System.out.println("no se puede agregar la hoja");
	}
	public void eliminar(Componente c)
	{
		System.out.println("no se puede quitar la hoja");
	}
	public void mostrar(int depth)
	{
		System.out.println('-' + "" + nombre);
	}
}
public class Client
{
	public static void main(String[] args)
	{
		Compuesto raiz = new Compuesto("root");
		raiz.agregar(new Hoja("hoja A"));
		raiz.agregar(new Hoja("hoja B"));
		Compuesto comp = new Compuesto("compuesto X");
		comp.agregar(new Hoja("hoja XA"));
		comp.agregar(new Hoja("hoja XB"));
		raiz.agregar(comp);
		raiz.agregar(new Hoja("hoja C"));
		Hoja l = new Hoja("hoja D");
		raiz.agregar(l);
		raiz.eliminar(l);
		raiz.mostrar(1);
	}
}

Código completo en C# editar

using System;
using System.Collections.Generic;

namespace WikipediaCompositePattern
{
    public class Program
    {
        static void Main(string[] args)
        {
            Compuesto raiz = new Compuesto("root");
            raiz.Agregar(new Hoja("hoja A"));
            raiz.Agregar(new Hoja("hoja B"));
            Compuesto comp = new Compuesto("compuesto X");
            comp.Agregar(new Hoja("hoja XA"));
            comp.Agregar(new Hoja("hoja XB"));
            raiz.Agregar(comp);
            raiz.Agregar(new Hoja("hoja C"));
            Hoja l = new Hoja("hoja D");
            raiz.Agregar(l);
            raiz.Eliminar(l);
            raiz.Mostrar(1);
        }
    }

    public abstract class Componente
    {
        protected string nombre;
        public Componente (string nombre)
        {
            this.nombre = nombre;
        }
        public abstract void Agregar(Componente componente);
        public abstract void Eliminar(Componente componente);
        public abstract void Mostrar(int profundidad);
    }

    public class Compuesto: Componente
    {
        private List<Componente> hijo = new List<Componente>();
        public Compuesto (string nombre): base(nombre) { }
        public override void Agregar(Componente componente)
        {
            hijo.Add(componente);
        }
        public override void Eliminar(Componente componente)
        {
            hijo.Remove(componente);
        }
        public override void Mostrar(int profundidad)
        {
            Console.WriteLine(string.Format("{0} nivel: {1}", nombre, profundidad));
            for (int i = 0; i < hijo.Count; i++)
                hijo[i].Mostrar(profundidad + 1);
        }
    }

    public class Hoja: Componente
    {
        public Hoja(string nombre): base(nombre) { }
        public override void Agregar(Componente componente)
        {
            Console.WriteLine("no se puede agregar la hoja");
        }
        public override void Eliminar(Componente componente)
        {
            Console.WriteLine("no se puede eliminar la hoja");
        }
        public override void Mostrar(int profundidad)
        {
            Console.WriteLine(string.Format("-{0}", nombre));
        }
    }
}