Las clases intermedias nos permiten modelar una relación entre dos clases en la que ninguna de las dos clases contiene a la otra y además la misma relación tiene algunos atributos. Por ejemplo, podemos decir que un vendedor tiene varias ventas de un mismo producto (y a la vez podemos decir que un producto es vendido por varios vendedores) y además, cada una de las ventas va a tener una fecha, pero no podemos decir que la fecha sea del vendedor y tampoco podemos decir que es del producto.
Entonces ¿qué podemos hacer? Podemos construir una tercera clase que nos dice cómo van a relacionarse las primeras dos. De esta manera podríamos crear una clase Venta que tiene la fecha de la venta y esta clase es la que relaciona a los vendedores y los productos.
Aunque es cierto que muchas veces podemos darle la vuelta a las clases intermedias utilizando algunos trucos de diseño, esto no siempre es lo más adecuado porque las clases intermedias nos dan ventajas como:
Como un ejemplo imaginemos que tenemos un diseño como el siguiente en el que utilizamos una clase intermedia:
En este diagrama podemos ver que debemos crear una clase intermedia llamada IngredientesDePlatillo (regularmente se utiliza un nombre que indique qué tipo de relación existe entre las instancias de la clases que relaciona) que es la que va a comunicar las dos clases Platillo e Ingrediente.
Primero que nada vamos a crear el código de estas tres clases en Java:
public class Dish { private String name; public Dish(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Ingredient { private String name; public Ingredient(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Como podemos ver estas dos primeras clases son exactamente iguales que las que hemos manejado siempre, la clase que va a tener algo nuevo es la siguiente, pero aún así no es algo muy diferente que lo que hemos venido manejando.
public class IngredientsOfDish { private Ingredient ingredient; private Dish dish; private int quantity; public IngredientsOfDish(Ingredient ingredient, Dish dish, int quantity) { this.ingredient = ingredient; this.dish = dish; this.quantity = quantity; } public Dish getDish() { return dish; } public void setDish(Dish dish) { this.dish = dish; } public Ingredient getIngredient() { return ingredient; } public void setIngredient(Ingredient ingredient) { this.ingredient = ingredient; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } }
Como podemos ver esta clase tiene en sus atributos una instancia de la clase Dish y una instancia de la clase Ingredient, estas instancias se van a utilizar para comunicar a las dos clases entre ellas. Podemos pensar en la clase IngredientsOfDish como un intermediario entre las dos anteriores.
Lo primero que hay que tomar en cuenta para utilizar las clases intermedias es que debemos tener dos listas (en este caso la lista de platillos e ingredientes) y que estas dos listas deben estar relacionadas por una tercera lista (en este caso la lista de ingredientes de cada platillo). Como lo primero que tenemos que hacer es crear estas listas debemos pensar en una interfaz gráfica que nos permita agregar platillos, ingredientes y una vez que hayamos agregados estos dos debemos pensar en la interfaz que nos permita relacionarlos.
Ya que tenemos construidas las clases vamos a utilizarlas en nuestra interfaz gráfica. Regularmente las clases relacionadas con clases intermedias se representan como una tabla o un árbol (porque son los componentes que nos permiten relacionar muchos objetos con muchos otros), en este caso vamos a utilizar una tabla para representar todas las recetas.
Primero que nada vamos a crear una lista de ingredientes y una lista de platillos dentro del constructor de la ventana:
public MainFrame() { initComponents(); ingredients = new Ingredient[] { new Ingredient("Pan"), new Ingredient("Queso"), new Ingredient("Tortilla") }; dishes = new Dish[] { new Dish("Mole con pollo"), new Dish("Quesadillas"), new Dish("Molletes"), new Dish("Enfrijoladas"), new Dish("Enchiladas") }; }
Estas listas las vamos a utilizar para construir nuestra lista de ingredientes de platillos, para hacer esto vamos a utilizar los ingredientes en el arreglo ingredients y los platillos en el arreglo dishes, donde por ejemplo dishes[0] sería “Mole con pollo”.
Para hacer esto vamos a utilizar el siguiente código:
public MainFrame() { [...] ingredientsOfDishes = new IngredientsOfDish[] { // Los molletes dishes[2] usan tres panes ingredients[0] new IngredientsOfDish(ingredients[0], dishes[2], 3), // Las quesadillas usan 100 gramos de queso new IngredientsOfDish(ingredients[1], dishes[1], 100), // Los molletes usan 150 gramos de queso new IngredientsOfDish(ingredients[1], dishes[2], 150), // Los molletes usan 75 gramos de queso new IngredientsOfDish(ingredients[1], dishes[4], 75), new IngredientsOfDish(ingredients[2], dishes[1], 3), new IngredientsOfDish(ingredients[2], dishes[3], 3), new IngredientsOfDish(ingredients[2], dishes[4], 3), new IngredientsOfDish(ingredients[2], dishes[1], 3), new IngredientsOfDish(ingredients[2], dishes[3], 3), new IngredientsOfDish(ingredients[2], dishes[4], 3), };
Como podemos ver el constructor de la clase IngredientsOfDish recibe una instancia de un ingrediente y una de un platillo ya que de esta manera las relaciona.
Ahora vamos a crear el modelo de la tabla para lo que necesitamos crear una matriz con los datos que se van a mostrar y un arreglo en el que se guardan los títulos que aparecen en la parte superior de la tabla. Primero vamos a crear los títulos, en la primera columna vamos a poner los platillos y en todas las demás vamos a poner los ingredientes para que de esta manera nuestra tabla nos indique por cada platillo qué cantidad de ingredientes es necesario utilizar.
public MainFrame() { [...] String titles[] = new String[ingredients.length+1]; titles[0] = "Platillo"; for (int i = 0; i < ingredients.length; i++) { Ingredient ingredient = ingredients[i]; titles[i+1] = ingredient.getName(); } }
Ahora vamos a crear los datos, para hacer esto vamos a utilizar un método poco eficiente pero que funciona. Normalmente para manejar esto se utilizan mapas, sin embargo para este ejemplo vamos a utilizar puros arreglos por lo que al final es necesario buscar el elemento en lugar de únicamente obtenerlo como se ve a continuación:
public MainFrame() { [...] // Ponemos el largo de dishes+1 para que podamos poner los ingredientes Object data[][] = new Object[dishes.length][ingredients.length+1]; // por cada platillo for (int i = 0; i < dishes.length; i++) { Dish dish = dishes[i]; // pongo el nombre del platillo en la primera columna data[i][0] = dish.getName(); // por cada ingrediente for (int j = 0; j < ingredients.length; j++) { Ingredient ingredient = ingredients[j]; // por cada union for (int k = 0; k < ingredientsOfDishes.length; k++) { IngredientsOfDish ingredientsOfDish = ingredientsOfDishes[k]; // si esta union es el ingrediente con el platillo actual if (ingredientsOfDish.getIngredient().equals(ingredient) && ingredientsOfDish.getDish().equals(dish)) { // pongo su cantidad data[i][j+1] = ingredientsOfDish.getQuantity(); } } } } }
Y ya sólo unimos todo y mostramos el modelo en la tabla:
public MainFrame() { [...] DefaultTableModel model = new DefaultTableModel(data, titles); tblQuantities.setModel(model); }
De esta manera obtendremos algo similar a lo siguiente: