Main todavía conoce demasiado
Nuestra aplicación funciona, pero Main sigue decidiendo qué hacer cuando el usuario pulsa los botones.
Producto_Vista ventana = new Producto_Vista();
ventana.setVisible(true);
ventana.listenerCancelar(e ->
ventana.limpiarCampos());
ventana.listenerAgregar(e -> {
long codigo = Long.parseLong(ventana.getTxtCodigo());
String nombre = ventana.getTxtNombre();
double precio = Double.parseDouble(ventana.getTxtPrecio());
int stock = Integer.parseInt(ventana.getTxtStock());
Producto p = new Producto(codigo, nombre, precio, stock);
listaProductos.add(p);
JOptionPane.showMessageDialog(ventana, listaProductos);
});
ventana.listenerEliminar(e -> {
JOptionPane.showConfirmDialog(ventana, "¿Está seguro?");
});
En el proyecto actual la clase Swing se llama Producto_Vista. Mantendremos ese nombre para que el material coincida con el código que ya tienen.
La Vista recibe la interacción. El Controller coordina la respuesta.
El Controller es el componente que escucha o recibe acciones originadas en la interfaz y organiza qué debe ocurrir a continuación.
Controller sí debería…
- registrar qué hacer ante los eventos;
- pedir datos a la Vista;
- coordinar una operación;
- decirle a la Vista cuándo debe limpiarse o actualizarse.
Controller no debería terminar…
- diseñando botones y paneles;
- acumulando todas las reglas del negocio;
- encargándose para siempre de guardar los datos;
- convirtiéndose en un nuevo
Maingigante.
Un paquete nuevo para una responsabilidad nueva
Dentro del proyecto creamos el paquete:
y dentro de él la clase:
package uy.edu.utu.programacion.controller;
public class ProductoController {
}
La clase ya existe, pero todavía no puede controlar nada porque no conoce la Vista.
ProductoController cree una ventana nueva por su cuenta, o que Main le entregue la misma ventana que ya creó?Main crea las piezas. El constructor las conecta.
El Controller necesita trabajar con la Vista existente. Por eso guardamos una referencia como atributo y la recibimos por el constructor.
package uy.edu.utu.programacion.controller;
import uy.edu.utu.programacion.view.Producto_Vista;
public class ProductoController {
private Producto_Vista vista;
public ProductoController(Producto_Vista vista) {
this.vista = vista;
}
}
¿Qué significa this.vista = vista?
El parámetro vista existe solamente durante la ejecución del constructor. Con this.vista guardamos esa referencia en el atributo del objeto para poder usarla después desde otros métodos del Controller.
Main conserva la tarea de construir y conectar los componentes principales.Empezamos por Cancelar porque es la acción más simple
Hoy la respuesta al botón está dentro de Main:
ventana.listenerCancelar(e ->
ventana.limpiarCampos());
La movemos al Controller. Pero antes de escribir código nuevo aparece una decisión de organización: ¿dónde vamos a registrar los listeners de los botones?
configurarEventos() no es un método de Java ni de Swing. Es un método que creamos nosotros para reunir en un mismo lugar las conexiones entre los eventos de la Vista y las acciones del Controller. Podríamos poner esas líneas directamente en el constructor, pero cuando tengamos varios botones el constructor empezaría a llenarse de listeners.Por eso agregamos al Controller un método privado llamado configurarEventos(). El nombre también es una decisión nuestra: describe su única tarea.
private void configurarEventos() {
// Aquí iremos registrando los eventos de la Vista
}
Ahora sí registramos el primer evento. Cuando la Vista avise que se pulsó Cancelar, el Controller ejecutará su método cancelar():
private void configurarEventos() {
vista.listenerCancelar(e -> cancelar());
}
ProductoController llama explícitamente a configurarEventos() cuando se crea el controlador.Después separamos la acción concreta en otro método:
private void cancelar() {
vista.limpiarCampos();
}
Finalmente hacemos que el constructor configure los eventos al crear el Controller:
public ProductoController(Producto_Vista vista) {
this.vista = vista;
configurarEventos();
}
configurarEventos() de cancelar()? Porque una cosa es decir qué evento dispara una acción y otra es definir qué hace esa acción. El código queda más fácil de leer y después podremos agregar más eventos sin llenar el constructor.¿Eso tiene un nombre?
Sí. Llamamos refactorización a modificar la estructura interna del código procurando mantener su comportamiento observable. La pantalla hace lo mismo; el código queda mejor organizado.
Ahora trasladamos una acción que tiene más pasos
Para mantener el mismo comportamiento que ya tiene el proyecto, por ahora el Controller también tendrá el ArrayList<Producto>. No asumimos que este sea el diseño definitivo.
import java.util.ArrayList;
import uy.edu.utu.programacion.model.Producto;
public class ProductoController {
private Producto_Vista vista;
private ArrayList<Producto> listaProductos;
public ProductoController(Producto_Vista vista) {
this.vista = vista;
this.listaProductos = new ArrayList<Producto>();
configurarEventos();
}
...
}
Registramos el evento:
private void configurarEventos() {
vista.listenerCancelar(e -> cancelar());
vista.listenerAgregar(e -> agregarProducto());
}
y trasladamos la lógica que antes estaba dentro de la lambda de Main:
private void agregarProducto() {
long codigo = Long.parseLong(vista.getTxtCodigo());
String nombre = vista.getTxtNombre();
double precio = Double.parseDouble(vista.getTxtPrecio());
int stock = Integer.parseInt(vista.getTxtStock());
Producto p = new Producto(codigo, nombre, precio, stock);
listaProductos.add(p);
JOptionPane.showMessageDialog(vista, listaProductos);
}
Misma idea, otro evento
En el proyecto actual el botón Eliminar todavía solo pregunta si el usuario está seguro. Trasladamos exactamente esa conducta; no inventamos aún el borrado completo.
private void configurarEventos() {
vista.listenerCancelar(e -> cancelar());
vista.listenerAgregar(e -> agregarProducto());
vista.listenerEliminar(e -> confirmarEliminar());
}
private void confirmarEliminar() {
JOptionPane.showConfirmDialog(vista, "¿Está seguro?");
}
Hasta ahora, ¿qué ganamos?
El cambio se hace visible cuando miramos el punto de entrada
Ahora Main puede quedar así:
package uy.edu.utu.programacion;
import uy.edu.utu.programacion.controller.ProductoController;
import uy.edu.utu.programacion.view.Producto_Vista;
public class Main {
public static void main(String[] args) {
Producto_Vista ventana = new Producto_Vista();
ProductoController controller =
new ProductoController(ventana);
ventana.setVisible(true);
}
}
Antes
Main creaba, escuchaba, leía campos, convertía, construía productos, guardaba y mostraba mensajes.
Ahora
Main crea los componentes principales, los conecta y pone en marcha la aplicación.
controller dentro de Main? No necesariamente. La dejamos porque hace explícito que existe un objeto Controller conectado con la Vista y porque más adelante le entregaremos también un Service.¿Qué pasa ahora cuando el usuario pulsa “Agregar”?
El botón genera un evento.
El listener registrado llama a agregarProducto().
Pide los textos a la Vista, los convierte y crea el objeto.
Lo agrega temporalmente a la colección y muestra el resultado.
La Vista sigue siendo pasiva
Los botones continúan privados dentro de Producto_Vista. La Vista solamente ofrece métodos como listenerAgregar(...) y getters como getTxtCodigo(). Así, el Controller no necesita acceder directamente a los componentes internos.
Ahora te toca mover responsabilidades
Crear ProductoController, recibir la Vista por constructor y trasladar únicamente la acción Cancelar.
Checkpoint: Main ya no contiene el listener de Cancelar.
Trasladar Cancelar, Agregar y Eliminar, usando configurarEventos() y métodos privados para cada acción.
Checkpoint: Main solo crea, conecta y muestra.
Sin cambiar el comportamiento, identificar con comentarios cuáles líneas de agregarProducto() son coordinación de interfaz y cuáles empiezan a parecer lógica que quizá debería vivir en otro componente.
Pista: pensá en el futuro Service.
Controller, Vista o Main
Elegí el componente que mejor encaja con cada responsabilidad en la arquitectura que estamos construyendo.
1. Registrar qué método debe ejecutarse cuando se pulsa “Agregar”.
2. Mantener privados los JButton y JTextField y exponer getters o métodos de listeners.
3. Crear la Vista y el Controller y conectarlos al iniciar el programa.
4. Pedir a la Vista el texto escrito en el campo precio después de que el usuario inicia la operación Agregar.
Resolvimos un problema… y apareció el siguiente
Logramos nuestro objetivo inmediato:
Pero ahora miremos con atención a ProductoController. Además de coordinar eventos, está:
- convirtiendo textos a números;
- creando objetos
Producto; - manteniendo un
ArrayList; - agregando productos a esa colección.
Micro-entrega de documentación
- Dibujá el recorrido de un clic:
Usuario → Vista → Controller. - Explicá en dos o tres líneas cuál es la responsabilidad principal de
ProductoController. - Registrá qué código salió de
Mainy qué comportamiento del programa se mantuvo igual. - Marcá al menos dos tareas que hoy tiene Controller y que sospechás que podrían pertenecer a otro componente.
Próximo material: ProductoService. Vamos a distinguir coordinación de interfaz, validación de entrada y lógica de negocio, y sacaremos del Controller las operaciones que no deberían depender de Swing.
