El Controller resolvió un problema… pero empezó a heredar otros
Después del material anterior, Main quedó pequeño. Ahora miremos con lupa el método agregarProducto() del Controller.
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);
}
En pocas líneas aparecen tareas bastante diferentes:
Pedir datos a la Vista.
Pasar texto a números.
Decidir si un producto es válido y crearlo.
Guardar el producto en la colección.
“No puedo convertirlo” no es lo mismo que “no debería aceptarlo”
Antes de crear ninguna clase nueva, comparemos dos entradas posibles para el precio.
El usuario escribe hola
Double.parseDouble("hola") no puede convertir ese texto en un número.
Es un problema de entrada o formato.
El usuario escribe -500
Java puede convertirlo perfectamente a double.
Pero una tienda no debería aceptar un precio negativo. Es una regla del negocio.
| Pregunta | Responsabilidad |
|---|---|
| ¿Lo escrito puede convertirse al tipo que necesitamos? | Controller, porque está interpretando la entrada que llega desde la Vista. |
| ¿Ese valor tiene sentido para nuestra aplicación? | Service, porque la regla seguiría existiendo aunque cambiáramos Swing por otra interfaz. |
Necesitamos un componente que conozca las operaciones del negocio
No vamos a crear ProductoService porque “la arquitectura dice que tiene que estar”. Lo necesitamos porque encontramos tareas que no deberían depender de la Vista.
Coordina la interacción: escucha eventos, lee la Vista, convierte entradas y decide qué mostrar.
Aplica reglas y realiza operaciones del negocio: crear, agregar, buscar, eliminar, etc.
Muestra componentes, recibe datos del usuario y expone eventos y valores.
JButton, JTextField ni JOptionPane.Primero, una clase vacía con una responsabilidad clara
Creamos el paquete:
y dentro:
package uy.edu.utu.programacion.service;
public class ProductoService {
}
Por ahora no hace nada. Antes de agregar métodos, decidimos qué responsabilidad vamos a trasladarle primero.
ArrayList<Producto> representa los productos con los que trabaja la aplicación; no es un detalle de la pantalla.El ArrayList sale del Controller y pasa al Service
En la versión anterior teníamos:
public class ProductoController {
private Producto_Vista vista;
private ArrayList<Producto> listaProductos;
...
}
Ahora el Service será quien mantenga esa colección:
package uy.edu.utu.programacion.service;
import java.util.ArrayList;
import uy.edu.utu.programacion.model.Producto;
public class ProductoService {
private ArrayList<Producto> listaProductos;
public ProductoService() {
listaProductos = new ArrayList<Producto>();
}
}
private? Porque queremos que los demás componentes pidan operaciones al Service, en lugar de modificar libremente su colección desde afuera.Necesitamos una operación para consultar los productos
En lugar de entregar la lista interna para que cualquiera pueda modificarla, devolvemos una copia:
public ArrayList<Producto> listarProductos() {
return new ArrayList<Producto>(listaProductos);
}
new ArrayList(...)? Construye otra lista con los mismos productos. Quien la recibe puede recorrerla o mostrarla sin tener acceso directo a la colección que administra el Service.Antes de crear un Producto, comprobamos las reglas del negocio
Para nuestra Tienda de Cómics estableceremos estas reglas mínimas:
- el código debe ser mayor que cero;
- el nombre no puede quedar vacío;
- el precio debe ser mayor que cero;
- el stock no puede ser negativo.
Ahora sí creamos una operación del Service:
public Producto agregarProducto(long codigo, String nombre,
double precio, int stock) {
if (codigo <= 0) {
throw new IllegalArgumentException("El código debe ser mayor que cero");
}
if (nombre == null || nombre.isBlank()) {
throw new IllegalArgumentException("El nombre no puede estar vacío");
}
if (precio <= 0) {
throw new IllegalArgumentException("El precio debe ser mayor que cero");
}
if (stock < 0) {
throw new IllegalArgumentException("El stock no puede ser negativo");
}
Producto producto = new Producto(codigo, nombre, precio, stock);
listaProductos.add(producto);
return producto;
}
JTextField. Recibe datos ya convertidos. Su pregunta no es “¿puedo transformar esto en un número?”, sino “¿estos valores cumplen las reglas de la aplicación?”.El Service debe poder avisar que una operación no puede realizarse
Acaba de aparecer una instrucción nueva, así que no la usamos como si fuera mágica.
throw new IllegalArgumentException("El precio debe ser mayor que cero");
throw significa: interrumpí esta operación y avisá que ocurrió un problema. En este caso usamos IllegalArgumentException porque los datos recibidos no cumplen las condiciones que exige el método.
El Service no muestra un JOptionPane. Solo comunica el problema. Quien conoce la interfaz decidirá cómo presentarlo.
Ahora le entregamos un Service al Controller
Igual que hicimos con la Vista, el Controller no crea el Service por su cuenta: lo recibe por el constructor y guarda una referencia.
private Producto_Vista vista;
private ProductoService service;
public ProductoController(Producto_Vista vista,
ProductoService service) {
this.vista = vista;
this.service = service;
configurarEventos();
}
Después simplificamos agregarProducto(). Primero intentamos convertir los textos. Si el formato es correcto, pedimos al Service que realice la operación.
private void agregarProducto() {
try {
long codigo = Long.parseLong(vista.getTxtCodigo());
String nombre = vista.getTxtNombre();
double precio = Double.parseDouble(vista.getTxtPrecio());
int stock = Integer.parseInt(vista.getTxtStock());
service.agregarProducto(codigo, nombre, precio, stock);
JOptionPane.showMessageDialog(
vista,
service.listarProductos()
);
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(
vista,
"Código, precio y stock deben ser números válidos"
);
} catch (IllegalArgumentException e) {
JOptionPane.showMessageDialog(
vista,
e.getMessage()
);
}
}
¿Por qué hay dos catch?
| Excepción | Qué significa aquí | Quién originó el problema |
|---|---|---|
NumberFormatException | No se pudo convertir un texto a número. | La entrada recibida desde la Vista. |
IllegalArgumentException | Los datos sí tienen el tipo correcto, pero violan una regla. | El Service detectó una condición inválida. |
JOptionPane. El Service sigue sin conocer Swing.Ahora Main arma la aplicación
Como el Controller necesita una Vista y un Service, Main crea ambos y se los entrega:
package uy.edu.utu.programacion;
import uy.edu.utu.programacion.controller.ProductoController;
import uy.edu.utu.programacion.service.ProductoService;
import uy.edu.utu.programacion.view.Producto_Vista;
public class Main {
public static void main(String[] args) {
Producto_Vista vista = new Producto_Vista();
ProductoService service = new ProductoService();
ProductoController controller =
new ProductoController(vista, service);
vista.setVisible(true);
}
}
¿Qué ocurre ahora cuando el usuario agrega un producto?
El usuario escribe los datos y pulsa Agregar.
Lee los textos e intenta convertir código, precio y stock.
Comprueba las reglas, crea el objeto y lo agrega a su colección.
Pide la lista y decide cómo mostrar el resultado en la Vista.
Probemos tres situaciones
| Entrada | Qué ocurre |
|---|---|
Precio = hola | Controller no puede convertirlo y muestra un mensaje de formato. |
Precio = -500 | Controller lo convierte; Service rechaza la regla y comunica el motivo. |
Precio = 500 | Service valida, crea el Producto y lo agrega a la colección. |
Ahora te toca separar el negocio del control
Crear ProductoService, mover allí el ArrayList y crear agregarProducto(...).
Checkpoint: Controller ya no tiene una colección de productos.
Agregar las cuatro reglas de negocio y adaptar Controller con los dos tipos de error: formato y regla inválida.
Checkpoint: Service no importa ninguna clase de Swing.
Agregar la regla “no puede existir otro producto con el mismo código”. La búsqueda debe hacerse dentro del Service.
Pregunta: ¿por qué esta regla necesita conocer la colección completa?
ArrayList y agregar; después incorporar reglas y excepciones.¿Controller o Service?
Elegí dónde ubicarías cada responsabilidad en la arquitectura que acabamos de construir.
1. Convertir el texto "250.50" recibido de un JTextField a double.
2. Comprobar que el precio sea mayor que cero.
3. Crear el objeto Producto y agregarlo a la colección cuando cumple las reglas.
4. Mostrar con JOptionPane el mensaje que explica por qué la operación fue rechazada.
5. Comprobar que no exista otro producto con el mismo código.
Ahora cada componente tiene una responsabilidad más clara
Crea y conecta los componentes principales.
Coordina la interacción entre la Vista y las operaciones de la aplicación.
Aplica reglas, crea productos y administra las operaciones sobre ellos.
Pero todavía hay una limitación evidente:
Micro-entrega de documentación
- Dibujá la arquitectura actual:
Vista → Controller → Service → ArrayList. - Escribí dos ejemplos de validación de formato y dos ejemplos de regla de negocio.
- Explicá por qué
ProductoServiceno debería usarJOptionPane. - Registrá qué responsabilidades salieron del Controller en esta refactorización.
Puente hacia lo que viene: hoy el Service guarda en un ArrayList. Cuando necesitemos persistencia, esa tarea dejará de ser responsabilidad del Service y aparecerá una nueva pieza: el acceso a datos.
