INTRODUCCIÓN A MVC

Modelos, Vistas y Controladores

Teoría

Historia

La idea de aplicar patrones de diseño al desarrollo de software nace a principios de los 80.

La principal razón para esto es que al irse produciendo programas mas complejos conviene utilizar una metodología estandarizada en su diseño y codificación.

Su uso presenta varias ventajas:

  • Si existe un patrón de diseño apto para nuestro problema se acortan los tiempos de desarrollo
  • El programa creado resulta mas fácil de leer y por lo tanto de mantener.
  • Si se trabaja en un equipo es mas fácil dividir las tareas a realizar
  • Es mas simple modificar componentes sin afectar a todo el sistema

Y algunas desventajas:

  • En programas "chicos" posiblemente se tarde más en adaptarlos a un patrón que si no se utilizara uno.

El patrón de diseño MVC nace durante el desarrollo del lenguaje Smalltalk durante las décadas del 70 y 80 (Smalltalk es un lenguaje de objetos puro, que sigue utilizándose). Esto hace de MVC un patrón antiguo, pero que con algunas modificaciones llega hasta nuestros dias con la ventaja de la experiencia obtenida con los años de uso.

Aplicando MVC en la programación web

Este patrón de diseño nos interesa debido a que lo utilizaremos durante el transcurso del curso, por ejemplo con los frameworks React y Express.

También se encuentra en otras librerías de uso común como ser AngularJS

¿Qué funciones tiene cada componente?

Vista: Interactúa con el usuario, le muestra los datos y recibe los eventos generados por el mismo.

Tener en cuenta que la Vista no tiene por qué ser exclusivamente visual. Puede tener salidas auditivas y tactiles (terminal braille, guantes VR, etc), así como también las entradas (por ej "Alexa", lectores de huellas, etc.)

Modelo: Se encarga del manejo de los datos, por ejemplo mediante el acceso a una base de datos, o a los valores obtenidos por interacción con los usuarios.

Esto puede incluir su lectura, escritura, modificación, adquisición, etc.

Algunas de sus funciones pueden estar por fuera de la aplicación web, por ejemplo un programa externo puede modificar la base de datos y nuestra aplicación solo leer sus valores.

Controladores: Manejan el intercambio de información entre el modelo y la vista.

Pueden transformar la información recibida para adecuarla al receptor.

Por ejemplo la vista le envía al controlador el mensaje "el usuario pulsó un botón" y el controlador le pide al modelo que efectúe la acción correspondiente sobre los datos. O en el sentido contrario el modelo le informa de cambios en sus datos, y el controlador se lo comunica a la vista.

En un diseño MVC puro no hay comunicación directa entre el modelo y la vista, todo el intercambio entre ellos pasa por los controladores

¿Donde encuentro cada componente?

Vista: Está principalmente en las partes HTML y CSS de la página, y en las funciones javascript encargadas de crear y/o modificar las anteriores cuando sea necesario (por ejemplo ante cambios en el modelo o en respuesta a una acción del usuario)

Modelo: Abarca a los elementos que contienen los datos (por ejemplo una base de datos), y el código javascript encargado de su consulta y manipulación (normalmente lo encontraríamos casi totalmente en el servidor, aunque puede haber parte en el cliente)

Controladores:Se encuentra en las funciones javascript encargadas de responder a los eventos generados tanto en la vista como en el modelo (los encontrariamos tanto en el cliente como en el servidor).

Bibliografía

Para los que le interese profundizar sobre el tema de los patrones de diseño en general y en especial el patrón MVC, se puede encontrar material de utilidad en:

  • Wikipedia: https://wikipedia.org/
  • en OpenLibra: www.openLibra.com se encuentran varios libros sobre el tema, con licencias libres y descarga legal.
  • el libro Patrones de Diseño de Erich Gamma, es un clásico sobre el tema, y aún útil aunque tenga sus años (es de 1995).

Casos de ejemplo

1- Casa de Comidas

Debemos crear un sitio para una casa de comidas con entrega a domicilio. Esta ya está funcionando sobre la base de pedidos telefónicos.

Antes de ponernos a programar debemos analizar lo que tenemos que hacer, identificando cada parte y en este caso viendo como utilizamos el modelo MVC.

En la implementación deseada deben existir distintas categorias de usuarios y de productos.

Categorias de usuarios

Visitante
solo puede hacer una navegación básica, no puede realizar pedidos, existen areas donde no puede ingresar
Usuario registrado
Puede realizar pedidos, puede acceder a todo el sitio.
Administrador
Puede acceder a todo el sitio, puede realizar cambios en los datos

Productos

Nombre
el nombre del producto
Código
para uso interno, no se muestra al usuario
Precio
precio por porción
Stock
de cuantas porciones disponemos, se debe actualizar con cada pedido
Categorías
por ejemplo: entradas, platos principales, postres, bebidas, platos especiales, ofertas, etc.
Descripción
una breve descripción del producto
Imagen
una o más fotos del producto

Implementación del patrón MVC

¿Qué contiene cada componente?

Modelos:
Necesitaremos dos: uno para representar a los usuarios, y otro a los productos. Dependiendo de la cantidad de datos que se estima manejar podrían utilizarse bases de datos o archivos json.
Vistas:
Por lo menos cuatro, una para cada categoría de usuario y otra para el ingreso (login)
Controladores:
El código de enlace entre las dos partes anteriores, debe responder a las acciones de los usuarios y a los cambios de los modelos (por ej. cuando cambia el stock se actualizaran las vistas correspondientes para todos los usuarios conectados)

Control de Planta

Vista 1

Control de Planta

Vista 2

Sobre este ejemplo

En este caso se toma como modelo una simulación del manejo de parámetros de una linea de producción industrial obtenidos mediante sensores y controlados mediante los actuadores correspondientes.

Los datos del modelo se acualizan periódicamente en forma aleatoria, y también cuando el usuario actúa sobre la vista.

La simulación es solo aproximada, en el caso real el cambio de valores,si bien es aleatorio puede presentar tendencias a repetir el sentido del cambio (es más probable que después de un aumento siga otro aumento que una disminución, y viceversa), y las respuestas a los controles no son instantáneas

La Vista 1 es principalmente numérica, mientras que la Vista 2 contiene algunos elementos gráficos.

Análisis del ejemplo 2

Simplificaciones

Con el fin de facilitar el análisis, en el ejemplo se realizan varias simplificaciones respecto a lo que sería una página a publicar.

  • Todos los componentes se encuentran a nivel cliente. No se está utilizando un servidor, el que se verá mas adelante en el curso. Esto implica que:
    • Los datos están incluidos dentro del código javascript cliente. En la vida real lo más común es que se encuentren del lado del servidor, en una base de datos, o en archivos con formato json, XML o similares.
    • Los cambios que se realizen sobre los datos se pierden al cerrar la página (existen mecanismos para que queden guardados en el navegador del cliente, pero no se utilizan aquí).
    • No se requiere implementar una capa de seguridad (lo que haga el usuario se queda en su navegador).
  • No se utilizan hojas de estilo (CSS) ni scripts externos al sitio (aparte de normalize.css, que ayuda a eliminar diferencias de visualización entre navegadores)
  • Si miran el código de la página verán que no es exactamente igual al analizado aquí, donde solo vemos los puntos principales relacionados al tema y pasamos por alto algunos otros que se verán mas adelante en el curso.

Código

El archivo ./recursos/script.js contiene el código de la página que no está directamente relacionado al MVC

Cada componente se encuentra dentro de un objeto separado, para mostrar como interactuan entre sí.

Analizamos la combinación de los objetos datos, controladores1 y vista1

El objeto "vista" nunca llama al objeto "modelo" y viceversa. Todas las comunicaciones pasan por el objeto "controladores"

Verán que utilizo como métodos (funciones dentro de los objetos) tanto funciones estandar (function () { }) como funciones flecha (() => { }). Esto es debido a la diferente forma en que tratan a la variable this.

Modelo: los datos del modelo se encuentran dentro de un objeto javascript ubicado en ./recursos/modelo1.js, el cual contiene también los métodos(funciones) mediante los cuales se comunica con el resto de la aplicación.


datos = {                       // el el objeto que contiene el modelo
    "data": [                   // arreglo de los objetos de datos
        {                       // un objeto que contiene la información de un dato 
            "nombre": "presión 1",  
            "valor": "7",
            "unidad": "atm",
            "minimo": "3",
            "maximo": "8",
            "control_subir": "true",
            "control_bajar": "true",
            "descripcion": "",
        },
        ...
    ],
    // Métodos
    verificar: (n, valor) => {          
        // verifica la validez de los datos antes de cambiarlos
        let dato = this.data[n];
        if (valor < dato.inicio) { valor = dato.inicio } else {
            if (valor > dato.fin) { valor = dato.fin }
        }
        return valor;
    }
    getCantidad: function () {          
        // retorna la cantidad de datos en el arreglo data[]
        return this.data.length;
    },    
    getItem: function (n) {             
        // retorna el dato en la posición n del arreglo
        return this.data[n];
    },
    setItem: function (n, valor) {
        // cambia el dato recibido (si es válido)  
        // y avisa a los controladores que cambió un valor
        valor = this.verificar(n, valor);
        this.data[n].valor = valor;
        controladores.cambio(n);
    },
    subir: function(n) {                
        // incrementa en 1 el valor de un dato
        this.setItem(n, parseInt(this.getItem(n).valor) + 1);
    },
    bajar: function(n) {                
        // decrementa en 1 el valor de un dato
        this.setItem(n, parseInt(this.getItem(n).valor) - 1);
    },
    cambiarValores: () => {             
        // simula la lectura de sensores relacionados con los datos 
        // se basa en el uso de las funciones Math.floor() y Math.random().
        // se actualiza periódicamente mediante un "setInterval" en scripts.js 
        // que se activa al ingresar por primera vez en la vista
        ...
    }
}         
                

Vista: La vista se encuentra distribuida entre los archivos ./index.html (su estructura básica), ./recursos/vista.css (sus estilos) y ./recursos/vista1.js (el javascript de generación del resto de la estructura de la vista, la presentación de datos, y la respuesta a las acciones del usuario y a los mensajes de los controladores).

vista1 = {                                      
    // el objeto  que contiene la vista (su parte javascript)                    
    generada: false,
    // variable boolean que indica si ya fue generado el HTML de la vista
    mostrar: function () {                      
        // método llamado desde script.js cuando se muestra esta vista                            
        // si es la primera vez que se ingresa a la vista la genera,
        // sino actualiza todos los datos
        if (!this.generada) {                   
            this.generar();
            this.generada = true;
        } else {
            this.actualizarTodo();              
        } 
        this.controlarEventos();               
        // llama al método que activa los eventos de la vista
    },
    generar: () => {
        // genera la estructura HTML y muestra los primeros datos
        // es una función "larga" ya que se genera un arbol DOM relativamente grande.
        let contenido = document.querySelectorAll("#vista1 #contenido")[0];
        // elemento DOM contenedorde los datos de la vista
        if (contenido.innerHTML == "") {
            // si no existe contenido lo crea
            // si ya existe no hace nada
            let fragment = document.createDocumentFragment();
            //crea un fragmento de HTML que luego se insertará en el contenedor
            // el resto del método se basa en el uso de las funciones:
            //      document.createElement("elem");
            //          donde "elem" puede ser casi cualquier elemento del DOM
            //          en este caso principalmente "div" y "p"  
            //      elem.classList.add("clase");
            //          agrega una clase al elemento "elem"
            //      elem.textContent = texto;
            //          agrega "texto" como contenido de elem
            valor.innerHTML = `${dato.valor}<span class="unidad">${dato.unidad}</span>`;
            // en esta linea tenemos:
            //      innerHTML: indica que el valor es HTML que irá dentro del elemento "valor" 
            //      las comillas invertidas ` ` señalan el uso de un "template literal" 
            //      donde ${variable} se reemplaza por el valor de la variable
            div.appendChild(valor);
            // agrega el elemento "valor" como último hijo del elemento "div"
            // Este esquema se repite hasta completar la vista  
    }
            
    actualizar: (i, dato) => {
        // actualiza la vista del elemento "elem" 
        // cada vez que los controladores informan de un cambio en los datos del modelo.
        // controla que el valor esté en rango aceptable (entre el mínimo y el máximo)

        let id = "valor" + i;
        let valor = document.getElementById(id);
        valor.innerHTML = `${dato.valor}<span class="unidad">${dato.unidad}</span>`;
        if (dato.minimo && dato.valor < dato.minimo ||
                dato.maximo && dato.valor > dato.maximo) {
            valor.parentElement.classList.add("alerta");
        } else {
            valor.parentElement.classList.remove("alerta");
        }
    },
    actualizarTodo: function () {
        // recorre todos los datos (a través de los controladores correspondientes)
        // actualizando la vista
        for (let i = 0; i < controladores1.cantidadDatos(); i++) {
            this.actualizar(i, controladores1.leerDatos(i));
        }
    },
    controlarEventos: () => {
      // Se detectan los eventos de la vista
      // Creo un solo "EventListener" sobre el elemento padre de los botones
      // y cuando se produce el evento consulto que hijo lo recibió
      let contenido = document.querySelectorAll("#vista1 #contenido")[0];
      contenido.addEventListener("click", (event) => {
        if (event.target.parentElement.dataset.parametro) {
          if (event.target.parentElement.classList.contains("controlSubir")){
            controladores1.cambiar(event.target.parentElement.dataset.parametro, "subir")
          } else {
            controladores1.cambiar(event.target.parentElement.dataset.parametro, "bajar")
          }
        }
      });
    }           
                

Controladores: Los controladores se encuentran dentro del objeto controladores1 en el archivo ./recursos/controladores1.js

El código es más sencillo que los anteriores, ya que en este caso los controladores solo actuan como puente entre el modelo y la vista.

let controladores1 = {                  
    // objeto que contiene a los controladores
    cantidadDatos: () => {              
           // pregunta al modelo la cantidad de datos
        return datos.getCantidad();
    },
    leerDatos: (i) => {                 
            // consulta al modelo por un dato en particular
        return datos.getItem(i)
    },
    cambio: (i) => {                    
            // avisa a la vista que hubo cambios en los datos del modelo
        vista1.actualizar(i, datos.getItem(i));
    },
    cambiar: (i, sentido) => {          
           // indica al modelo que el usuario actuó sobre la vista
           // solicitando subir o bajar un valor
        if (sentido == "subir") {
            datos.subir(i);
        } else {
            datos.bajar(i);
        }
    }
};
                    
                

Ejercicios

  1. Gracias a lo bien que realizamos el sitio para la casa de comidas del ejemplo 1 👏 nos contratan para realizar uno similar para un gran supermercado.
    • ¿Qué similitudes y diferencias encuentran entre los dos casos?
    • ¿Podemos reutilizar parte del código de la casa de comidas?
    • ¿Cuáles son los principales cambios que tendriamos que realizar?
  2. (tarea para el hogar 😞) Imaginen que deben crear un sitio sobre el tema que ustedes deseen.

    Deben presentar:

    • Introducción al tema sobre el que quieran trabajar
    • ¿Cómo aplicarían la metodología MVC en este sitio?
    • ¿Qué contenidos tendría cada una de las partes (Modelo, Vista, Controlador)?