Ejemplo de MVC y diseño por capas: el Controlador


En la entrada anterior vimos una implementación de nuestra capa de Modelo utilizando MySQL como RDBMS y JPA para el mapeo objeto-relacional. En esta nueva entrada veremos una implementación de nuestra capa Controlador utilizando un simple esquema de paso de mensajes.

Antes de comenzar, es bueno recordar que en esta capa debemos implementar la lógica de negocios que debe satisfacer nuestra aplicación. Por ejemplo, supongamos que cuando agregamos un nuevo Fabricante debemos crear una entrada de log en alguna tabla para saber qué usuario efectuó dicha operación y en qué momento lo hizo (marca de tiempo o time-stamp). O supongamos que debemos notificarle a algún usuario particular por e-mail cuando dicha operación se ha efectuado. Pues bien, este tipo de requerimientos deben satisfacerse en la capa Controlador.

No obstante, nuestro ejemplo de implementación es muy sencillo y sólo actúa como intermediario o man-in-the-middle entre el Modelo y la Vista, teniendo el recaudo de ofrecer una interfaz a la Vista que encapsula las llamadas a los métodos del Modelo. De este modo, si fuera necesario implementar los requerimientos mencionados en el párrafo anterior, sólo deberíamos modificar la implementación del Controlador.

Nuestro controlador en Java: package example.mvc.controller

Llegado el momento de escribir nuestra capa Controlador en Java, recordemos que en nuestro diseño sólo tenemos la interfaz IFabricantesController. Por lo tanto definiremos un package llamado example.mvc.controller el cual contendrá nuestra interfz y su correspondiente implementación. En esta capa no será necesario agregar ningún artefacto más allá de la mencionada implementación de nuestra interfaz.

Package example.mvc.controller

IFabricantesController

He aquí el código de nuestra interfaz Controlador. Nótese que en la sección de imports hay una referencia a la interfaz de la Vista, la cual aun no tenemos disponible pero que será incluida en la siguiente entrada. Esto obedece a que en nuestro diseño, la comunicación entre Controlador y Vista se efectúa de manera directa y por lo tanto es necesario que ambas capas mantengan una referencia entre sí. En la entrada anterior vimos que en el caso del Controlador y el Modelo la comunicación es diferente y el Modelo no necesita mantener una referencia del Controlador, por lo cual está completamente desacoplado de su capa inmediata superior.

package example.mvc.controller;

import example.mvc.model.IFabricantesModel;
import example.mvc.view.IFabricantesView;

/**
 * @author Delcio Amarillo
 */
public interface IFabricantesController {
    
    public void agregarFabricante(IFabricantesView view);
    
    public void modificarFabricante(IFabricantesView view);
    
    public void eliminarFabricantes(IFabricantesView view);
    
    public void getListaFabricantes(IFabricantesView view);
    
    public void setFabricantesModel(IFabricantesModel model);
}

FabricantesControllerImp

Antes de ver el código de nuestra clase FabricantesControllerImp, que implementa la interfaz IFabricantesController, resulta oportuno recordar cómo es el paso de mensajes entre capas para entender mejor la lógica de implementación. Tomemos por ejemplo el diagrama de secuencia para agregar un nuevo Fabricante (el resto puede consultarse en la entrada Ejemplo de MVC y diseño por capas: Introducción):

SD - Agregar fabricante

Como podemos observar, la Vista solicita al Controlador que agregue un nuevo Fabricante, este a su vez delega la operación en el Modelo y finalmente notifica a la Vista el resultado de la operación. El mismo criterio de diseño se aplica al resto de los casos de uso.

Con esto en mente, veamos el código de nuestra implementación:

package example.mvc.controller;

import example.mvc.model.Fabricante;
import example.mvc.model.IFabricantesModel;
import example.mvc.view.IFabricantesView;
import java.util.List;

/**
 * @author Delcio Amarillo
 */
public class FabricantesControllerImp implements IFabricantesController {
    
    private IFabricantesModel model;
    
    public FabricantesControllerImp(IFabricantesModel model) {
        this.model = model;
    }

    @Override
    public void agregarFabricante(IFabricantesView view) {
        Fabricante fabricante = view.getNuevoFabricante();
        int codigoOperacion = model.insertar(fabricante);
        Boolean resultado = (codigoOperacion == IFabricantesModel.EXITO);
        view.notificarFabricanteAgregado(resultado);
    }

    @Override
    public void modificarFabricante(IFabricantesView view) {
        Fabricante fabricante = view.getFabricanteAModificar();
        int codigoOperacion = model.modificar(fabricante);
        Boolean resultado = (codigoOperacion == IFabricantesModel.EXITO);
        view.notificarFabricanteModificado(resultado);
    }

    @Override
    public void eliminarFabricantes(IFabricantesView view) {
        List<Fabricante> fabricantes = view.getFabricantesAEliminar();
        int codigOperacion = model.eliminar(fabricantes);
        Boolean resultado = (codigOperacion == IFabricantesModel.EXITO);
        view.notificarFabricantesEliminados(resultado);
    }
    
    @Override
    public void getListaFabricantes(IFabricantesView view) {
        List<Fabricante> fabricantes = model.getFabricantes();
        view.setListaFabricantes(fabricantes);
    }

    @Override
    public void setFabricantesModel(IFabricantesModel model) {
        this.model = model;
    }
}

Nótese que en todo momento hacemos uso de nuestro atributo model el cual no se inicializa dentro de nuestra clase, sino que debe ser inicializado en el exterior y bien pasado como parámetro al inicializar la clase FabricantesControllerImp o bien utilizando el setter correspondiente. Por ejemplo:

EntityManager entityManager = EntityManagerProvider.getProvider().getEntityManager();
IFabricantesModel model = new FabricantesModelJpa(entityManager);
IFabricantesController controller = new FabricantesControllerImp(model);

De ese modo nuestro Controlador queda totalmente desacoplado de la implementación del Modelo e incluso podemos cambiar de Modelo en tiempo de ejecución y las consecuentes llamadas al Controlador seguirán funcionando como es de esperar, haciendo que la implementación subyacente del Modelo sea totalmente transparente para la Vista:

IFabricantesController controller = new FabricantesControllerImp(modelImp1);
...
controller.setFabricantesModel(modelImp2);
...
controller.setFabricantesModel(modelImp3);

Una vez más, podemos dar fe de las ventajas del diseño utilizando interfaces.

Conclusión

En esta entrada vimos una implementación sencilla de nuestra capa Controlador, basada en un simple mecanismo de paso de mensajes con la Vista y delegación hacia el Modelo. Sin embargo, cabe destacar una vez más la robustez del diseño en el sentido que ante la necesidad de cumplir algún requerimiento de negocios sólo sería necesario modificar el código de nuestro Controlador. En la siguiente entrada veremos un ejemplo de implementación de la Vista utilizando Swing para crear nuestra interfaz de usuario.

Código fuente

Pueden encontrar el código del ejemplo DemoModelViewController en GitHub El mismo tiene la estructura de un proyecto Maven, con un archivo pom.xml y una carpeta src donde se encuentra el código fuente.

3 comentarios en “Ejemplo de MVC y diseño por capas: el Controlador

  1. Hernán

    Hola Delcio, te hago una consulta: en la clase FabricantesControllerImp, en el método ‘getListaFabricantes’, usás la interface para invocar al método ‘getFabricantes()’ en ‘model.getFabricantes();’. Ahora bien, esa variable ‘model’ no está inicializada, no llama a ningún constructor, entonces, ¿devuelve algo al ejecutar esa sentencia? Te lo pregunto porque usé este ejemplo para el DAO del ‘AbstractTableModel’, le puse una interface que es implementada por ese DAO para poder invocar desde el controlador a los métodos del DAO a través de esa interface, pero me tiró una NullPointerException, hasta que inicialicé esa variable ‘interface’ como ‘MiClaseDAO’ concreta. No sé si soy claro. Una vez que lo hice funcionó normalmente, pero no sé si eso es ‘desacoplar’. Saludos!

    Me gusta

    Responder
    1. Delcio Amarillo Autor

      Hola Hernán. Si, en realidad deberías pasarle el modelo inicializado, ya sea a través del constructor o usando el método ‘setFabricantesModel(IFabricantesModel model)’, antes de llamar a ‘getListaFabricantes()’. Gracias por tu comentario, ahora agrego un ejemplo de uso 😀

      Me gusta

      Responder

Deja un comentario