Profe Macón
Programación Avanzada
2.º BT · Tecnologías de la Información
PM
Tienda de Cómics · Arquitectura de componentes

Cuando Main hace demasiado

El programa funciona. Ese no es el problema. El problema es que Main empezó a conocer demasiadas cosas y a tomar demasiadas decisiones. Vamos a entender por qué conviene separar responsabilidades antes de crear ProductoController y ProductoService.

CET1ModularidadMantenibilidadReutilizaciónEscalabilidadPatrones
1 · Punto de partida

Primero: nuestro programa funciona

No estamos corrigiendo un error de sintaxis ni un botón roto. Estamos entrando en otro tipo de problema: cómo organizar un programa que crece.

En el proyecto actual, Main crea la lista de productos y la ventana, registra las acciones de los botones, obtiene los datos escritos por el usuario, convierte esos datos, crea un Producto y lo agrega a la colección.

public static void main(String[] args) {

    ArrayList<Producto> listaProductos =
            new ArrayList<Producto>();

    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?");
    });
}
Idea clave: que el código funcione no significa que su organización vaya a seguir siendo cómoda cuando agreguemos más funcionalidades.
2 · Radiografía

¿Cuántos trabajos está haciendo Main?

Leamos el código por responsabilidades, no por líneas.

1
Datos
Mantiene el ArrayList<Producto>.
2
Arranque
Crea y muestra la ventana.
3
Eventos
Decide qué ocurre al pulsar botones.
4
Entrada
Lee los datos de los JTextField.
5
Conversión
Transforma texto en long, double e int.
6
Modelo
Construye objetos Producto.
7
Operaciones
Agrega elementos a la colección.
8
Interfaz
Muestra mensajes con JOptionPane.
Pregunta guía: si dentro de tres meses queremos cambiar una regla de productos, ¿deberíamos tener que buscar esa regla dentro del mismo método que arranca toda la aplicación?
3 · Cuando el sistema crece

El problema todavía es pequeño… porque la aplicación todavía es pequeña

Imaginemos que continuamos agregando funcionalidades sin cambiar la organización actual.

Agregar → Eliminar → Modificar → Buscar → Ventas → Clientes → Base de datos

Si cada nueva función termina dentro de Main, ocurre algo previsible: cualquier cambio obliga a volver siempre al mismo lugar.

Más difícil de leerPara encontrar una tarea hay que recorrer código de muchas tareas diferentes.
Más fácil de romperModificar una parte aumenta la posibilidad de afectar otras que estaban funcionando.
Más difícil de reutilizarLa lógica queda pegada al arranque y a una interfaz concreta.
No existe una cantidad “correcta” de líneas para Main. El problema no es que tenga 20, 50 o 100 líneas. El problema es que acumule responsabilidades que deberían poder cambiar por motivos diferentes.
4 · Una idea de diseño

Una clase debería tener una responsabilidad principal clara

En diseño orientado a objetos aparece una regla muy útil: intentar que cada clase tenga una responsabilidad principal bien identificable.

Todo mezclado

Main arranca, escucha, valida, crea, guarda y muestra.

Un cambio en casi cualquier parte de la aplicación puede llevarnos a la misma clase.

→

Responsabilidades separadas

Cada componente resuelve un tipo de problema y colabora con los demás.

Los cambios quedan más localizados.

¿Esto tiene relación con SOLID?

Sí. La idea se relaciona con el Principio de Responsabilidad Única (SRP), la “S” de SOLID. Una formulación más precisa dice que una clase debería tener un motivo principal de cambio. Por ahora no necesitamos memorizar los cinco principios: nos alcanza con reconocer el problema que este principio intenta evitar.

Una buena pregunta: “¿Cómo explicaría en una sola frase para qué existe esta clase?”. Si necesitamos responder con una larga lista de tareas no relacionadas, probablemente convenga revisar el diseño.
5 · Modularidad

Separar para poder cambiar

La modularidad consiste en organizar un sistema en partes con responsabilidades claras y relaciones controladas entre ellas.

Crear paquetes ayuda a ordenar físicamente los archivos, pero modularidad no significa solamente crear carpetas. Si dividimos el código en cinco paquetes pero todos dependen de todos, el problema continúa.

MantenibilidadEncontrar y modificar una responsabilidad sin recorrer toda la aplicación.
ReutilizaciónUsar una parte en otro contexto sin arrastrar todo el sistema.
EscalabilidadAgregar funcionalidad sin tener que rediseñar todo desde cero.
ComprensiónPensar el sistema por partes y no como un bloque único.

Dos conceptos que vamos a volver a encontrar

CohesiónUna clase tiene buena cohesión cuando sus tareas están relacionadas entre sí y responden a una finalidad clara.
AcoplamientoDescribe cuánto depende un componente de los detalles de otro. Más adelante las interfaces nos ayudarán a reducir dependencias innecesarias.
6 · Arquitectura

Antes de programar más, decidimos dónde debería vivir cada responsabilidad

La arquitectura de software nos ayuda a pensar cuáles son las partes principales de una aplicación, qué responsabilidad tiene cada una y cómo se comunican.

Main · ensambla e inicia la aplicación
↓
Vista · presenta información y recibe interacción
↕
Controller · coordina las acciones del usuario
↓
Service · operaciones y reglas del negocio
↓
DAO · persistencia de datos (más adelante)

Las líneas punteadas representan componentes que todavía no construimos. Este es un mapa de llegada, no código para copiar ahora.

Objetivo de Main: que se encargue principalmente de crear las piezas, conectarlas e iniciar la aplicación. El trabajo cotidiano de la aplicación debe ocurrir fuera de él.
Un término opcional: “composition root”

En aplicaciones más grandes se llama composition root al lugar donde se crean y conectan los objetos principales. Nuestro Main se acercará a ese papel. No necesitamos usar este término para programar bien, pero explica por qué un Main pequeño puede seguir siendo importante.

7 · Patrones

No somos los primeros en encontrarnos con estos problemas

En programación aparecen problemas de diseño que se repiten. Un patrón describe una forma conocida de organizar una solución. No es un bloque de código que se copia sin pensar: es una idea que se adapta al problema concreto.

NivelQué organizaEjemplos
Patrones de diseñoColaboraciones concretas entre clases u objetos.Observer, Strategy, Factory.
Patrones / estilos arquitectónicosLa organización general de partes importantes de una aplicación.MVC, arquitectura en capas.
Ya vimos una idea relacionada con un patrón: los listeners de Swing trabajan mediante notificaciones de eventos, una idea estrechamente relacionada con Observer. No necesitamos estudiarlo completo todavía.

¿Qué vamos a utilizar nosotros?

En Tienda de Cómics construiremos una arquitectura en capas y, en la parte de interacción, tomaremos la separación de responsabilidades propia de MVC: una Vista que muestra, un Controller que coordina y componentes de negocio que resuelven operaciones.

Precisión importante: no necesitamos afirmar que estamos implementando “MVC puro”. Estamos usando una arquitectura pedagógica en capas, inspirada en esa separación, adaptada a nuestra aplicación Swing y a los objetivos del curso.
8 · Nuestro mapa de avance

No vamos a crear todas las capas de golpe

Cada componente aparecerá cuando el código nos muestre qué problema viene a resolver.

Ahora

Problema: Main coordina demasiadas acciones.

Siguiente solución: ProductoController.

Después

Problema: Controller empieza a acumular reglas y operaciones.

Solución: ProductoService.

Más adelante

Problema: los datos del ArrayList desaparecen al cerrar.

Solución: DAO + JDBC + base de datos.

Main → Controller → Service → DAO / BD

La arquitectura no se aprende repitiendo ese dibujo. Se aprende entendiendo por qué cada flecha y cada componente terminaron siendo necesarios.

9 · Comprobamos la idea

¿Qué tipo de componente debería hacerse cargo?

Elegí una opción. No estamos evaluando sintaxis: estamos pensando responsabilidades.

1. Cambiar el color y el texto del botón “Agregar”.

2. Recibir el clic en “Agregar” y coordinar qué debe ocurrir a continuación.

3. Decidir que un producto no puede guardarse con precio negativo.

4. Crear los componentes principales al iniciar el programa y conectarlos.

Importante: estas fronteras no son leyes físicas. En proyectos reales existen variantes. Lo que buscamos es poder justificar dónde colocamos cada responsabilidad y evitar que todo termine nuevamente en una sola clase.
10 · Cierre

La pregunta ya cambió

Al principio preguntábamos:

“¿Cómo hago para que el botón Agregar funcione?”

Ahora podemos formular una pregunta de diseño:

“¿Qué componente debería hacerse responsable de coordinar esa acción?”

Ese cambio de pregunta marca el paso de escribir funcionalidades aisladas a diseñar una aplicación que pueda crecer.

Micro-entrega de documentación

Antes de continuar, registrá en el cuaderno o documento del proyecto:

Main creció → separamos responsabilidades → próximo: ProductoController

Próximo material: vamos a crear ProductoController y trasladar allí la coordinación de eventos que hoy está dentro de Main, sin cambiar lo que el usuario ve.

Material en curso
← Índice del curso