Home » Guías » Patrones de diseño en software (III) : MVC
Patrones de Diseño, MVC

Patrones de diseño en software (III) : MVC

El padre de todos. El Odín de los patrones para los que trabajan en desarrollo Apple. El patrón Modelo-Vista-Controlador o MVC para los amigos. El patrón que Steve Jobs utilizó en 1988 para el sistema de desarrollo de sus ordenadores NeXT y que años después se popularizaría incluso para web. Vamos a ver un poco de historia del mismo y en qué consiste exactamente.

MVC, made by Xerox PARC

Apple no sería lo que es sin el Centro de Investigación de Palo Alto de Xerox. No habría habido revolución alguna si Steve Jobs no hubiera visto el potencial de lo que allí se hacía, cosa que la propia Xerox no supo ver. El conocido como Xerox PARC es la verdadera fuente de revolución pues crearon la informática tal como la conocemos hoy día. Pero obviamente, un trabajo en un almacén no puede revolucionar el mundo si alguien no lo descubre, y esa persona fue obviamente el señor Jobs y su equipo.

En 1979, Trygve Reenskaug, científico computacional y profesor emérito de la Universidad de Oslo (de procedencia noruega) fue el gran responsable de crear este patrón como modelo para diseño de interfaces gráficas de usuario. Un concepto completamente extraño el año en que fue creado.

MVC fue concebido como una solución general al problema de usuarios controlando un enorme y complejo conjunto de datos (Trygve Reenskaug).

La primera versión fue integrada en el lenguaje Smalltalk-76, integrándolo posteriormente como librería del sistema en la versión Smalltalk-80, en la que Brad Cox y Tom Love se basaron para crear StepStone, más conocido como Objective-C, en 1980. Lenguaje que sería licenciado por NeXT en 1988 como lenguaje de orientación a objetos para sus sistemas y donde estaría integrado MVC, a través de la conexión de sus primeros frameworks AppKit y FoundationKit, unidos a la app Interface Builder.** Interface Builder fue el primer IDE en permitir la construcción de una GUI (Graphic User Interface o interfaz gráfica de usuario) directamente arrastrando y soltando elementos desde una paleta de componentes a una ventana**, todo ello usando este modelo MVC para conectar los datos y la vista a través del controlador.

Interface Builder para NeXTStep

En su famosa entrevista del año 1995 que podemos ver en el documental “The Lost Interview”, Steve Jobs ya hablaba sobre este patrón y su orientación a objetos, descubierto en el Xerox PARC y que supuso la revolución que él necesitaba en los modelos de desarrollo del momento, enfocados en interfaces de texto y no en GUIs. MVC unida a la orientación a objetos supuso la revolución que la tecnología necesitaba porque hasta ese momento las GUIs se diseñaban con las mismas herramientas que creaban experiencias en modo texto y suponían un problema de tiempo y calidad muy importante para el desarrollo a finales de los 80.

“La innovación en la industria está en el software pero no ha habido una verdadera revolución en cuanto a cómo creamos software en los últimos 20 años, ha ido a peor. Mientras el Macintosh fue una revolución para el usuario final, haciéndolo más fácil de usar, ha sido lo contrario para los desarrolladores. Estos han pagado el precio y el software se ha vuelto cada vez más complejo de escribir para ser fácil de usar para el usuario final (…) El software se va a convertir en una fuerza increíble en este mundo, para proporcionar nuevas cosas y servicios a la gente, a través de internet y otros medios… va a ser un verdadero gran cambio en toda la sociedad. Hemos cogido otra de las grandes ideas que vimos en el Xerox PARC en el año 1979, que en aquel momento no vimos del todo clara, llamada tecnología de orientación a objetos. Y la hemos perfeccionado y comercializado. Nos hemos convertido en su mayor proveedor para el mercado. Esta tecnología de objetos te permite construir software 10 veces más rápido. Y eso es lo mejor.”

Esa tecnología era la combinación de la orientación a objetos, el lenguaje Objective-C y la aplicación del patrón de diseño MVC. Lo que convirtió a NeXT en la compañía más potente y revolucionaria del mundo del desarrollo y que hizo que todos se fijaran en ella para evolucionar sus sistemas a partir de ese momento. Además, WebObjects, el primer servidor de aplicaciones web de la historia (que funcionaba en Objective-C en sus primeras versiones) hizo que el modelo MVC llegara a la web y sentó los estándares de cómo funciona la Worldwide Web hoy día (precisamente a mediados de los años 90).

Concepto

Modelo, vista y controlador. Estos tres componentes son los que definen no solo las entidades si no la forma en que se comunican unas con otras. MVC es un patrón de diseño basado en orientación a objetos.

El modelo es tu aplicación. Es toda la funcionalidad de la misma, desarrollada en un determinado lenguaje de programación (Swift u Objective-C, según elección). No define cómo se muestra la información, solo lo que se hace con dicha información y dónde está almacenada. Si hacemos una app de gestión de tareas, el modelo es donde están almacenadas las tareas y donde se realizan procesos que deriven de esos datos.

El controlador es la lógica de la interfaz. Es decir, la forma en que definimos cómo aquello que hay en el modelo será mostrado en la pantalla. De esta forma definimos que las tareas serán presentadas en una vista de tabla donde cada celda tendrá un registro de nuestra base de datos de tareas y que en cada celda se mostrará una descripción, debajo la hora de la misma y a la derecha su prioridad (por ejemplo).

La vista son los componentes que definimos para hacer que aquello que queremos mostrar se muestre. Si hemos decidido en el controlador que nuestras tareas estarán en una vista de tabla, quiere decir que hemos de crear una. Si queremos una celda con 3 campos, hemos de crearlos y darles un tamaño y ubicación. Pero la vista, y esto es sumamente importante de entender, es ignorante sobre lo que hace o para qué sirve. Solo son huecos donde nosotros vamos a poner cosas, pero ella no sabe qué funcionalidad habrá detrás ni por qué mostrará un dato u otro. Eso lo definirá el controlador recogiendo los datos del modelo. En la vista podemos poner un botón que se pulse, pero la vista no sabe nada más que aquello es un botón y que se pulsa. Si ponemos 3 etiquetas para mostrar la información de nuestra lista de tareas, para la vista son solo 3 etiquetas. Nada más. No tiene lógica alguna, solo muestra datos.

Comunicación controlador-modelo

Cómo se comunican estos componentes y el flujo normal de esta es casi tan importante como el propio modelo. Lo primero a entender es que el modelo jamás se comunica con la vista o viceversa. Eso rompería el patrón como tal. Las comunicaciones han de ser siempre a través del controlador y saber en qué direcciones se podrá hacer define el patrón. El por qué es simple: el modelo es independiente de cualquier tipo de visualización. Solo procesa y almacena datos. Y de igual manera, la vista no tiene lógica alguna: solo son huecos donde poner cosas.

El primer canal de comunicación es el que inicia el controlador hacia el modelo. Es la forma en que el controlador recoge los datos del mismo para formatearlos y prepararlos con el objetivo de presentarlos. Al controlador no le interesa qué hace el modelo con esos datos, dónde los guarda, qué procesos realiza, cómo los obtiene… el controlador pide el dato y el modelo se lo proporciona.

De esta forma, y aquí está la clave del modelo de abstracción, podríamos cambiar toda la estructura de almacenamiento de una app, su modelo de base de datos, la forma en que los datos se obtienen, mejorar los procesos, optimizarlos… podríamos hacer lo que quisiéramos dentro del modelo y cambiarlo un millón de veces: todo seguirá funcionando mientras el controlador pida el dato X y el modelo se lo proporcione. He ahí la abstracción como tal que divide cada componente del MVC.

El controlador puede igualmente, no solo solicitar información al modelo, también puede actualizarlo porque el usuario haya cambiado algún valor o hecho algo que tiene que ver con él. Por ejemplo, actualizar o añadir una nueva tarea. A grandes rasgos, la comunicación del controlador al modelo sucede para actualizar datos del mismo, mientras que el modelo puede notificar al controlador de cualquier cambio para que este reacciona en consecuencia. De una u otra forma, la comunicación siempre parte del controlador hacia el modelo.

Comunicación controlador-vista

El otro importante canal de comunicación es del controlador a la vista. La vista tiene unos huecos, pero estos han de ser cargados con datos. Para ello el controlador usa los outlets: conexiones entre propiedades dentro del código del controlador y los elementos visuales (objetos) dentro de la vista. De esta forma, si yo tengo una propiedad que es un objeto UILabel llamado descripcion en mi código del controlador, puedo asociarlo mediante un outlet al correspondiente elemento visual en la vista. Esto crea una referencia entre mi objeto UILabel y lo que hay en pantalla, con lo que cualquier cambio en las propiedades de este objeto tendrán reflejo en la interfaz y viceversa. Si yo actualizo la propiedad text que define lo que se muestra en dicha etiqueta, el campo en la vista actualizará igualmente su valor a través de esta conexión. Y si actualizo un campo de texto en la vista, su valor se verá modificado en la propiedad asociada mediante el outlet dentro del controlador. Porque ambos son referencias a un mismo objeto.

En el caso del ejemplo, hemos creado una propiedad que hemos asociado a través de un outlet a un elemento de la interfaz. La cláusula @IBOutlet le dice al compilador que ahí hay una conexión. El ser de tipo weak indica que, aunque nuestro objeto está asociado a un campo y por lo tanto tendría que tener una referencia más a dicho objeto, queremos que se haga leve. Es decir, que no cuenta como una referencia más porque la gestión no depende de la propia vista. De esta forma, si la propiedad termina su ciclo de vida, aunque la interfaz siga existiendo esta podrá eliminarse de memoria sin problema. Y al revés, si la interfaz es descargada de memoria, el objeto no quedará retenido por existir una propiedad asociada a través de un outlet y se liberará correctamente sin provocar fugas o ciclos de retención.

Esto es posible porque se entiende que el ciclo de vida del controlador de una vista es el mismo que el de la vista en sí y ambos se destruyen a la vez, lo que evitará que puedan producirse errores en la gestión de memoria.

Vemos igualmente que hemos creado una variable en vez de una constante, y esto es debido a que yo podría cambiar el outlet a otro elemento de la vista en cualquier momento. Y además, estamos usando un tipo opcional UILabel! que igualmente nos obliga a que este objeto esté almacenado en una variable. ¿Y por qué el tipo es un opcional desempaquetado implícitamente? Porque como el elemento visual asociado a esta propiedad siempre va a existir, le estamos diciendo al sistema que el tipo ya viene declarado e inicializado: que nosotros en nuestra variable solo vamos a guardar una referencia a un objeto que pertenece, en este caso, a la vista y que ya existe y está inicializado como tal. Por lo tanto descripcion almacenará su referencia.

Eventos de interfaz, las acciones

Las comunicaciones entre vista y controlador acaban ahí. Solo con los outlets. Pero todos sabemos que una interfaz puede generar eventos a los que el controlador deberá responder. Estamos hablando entonces de los targets o acciones. Cuando pulsamos un botón en la vista asociamos su pulsación a la ejecución de un método del controlador a través de una acción.

En el ejemplo, tenemos una función de tipo @IBAction que será ejecutada cuando pulsemos el botón. Y en el parámetro sender enviaremos la referencia del botón que ha sido pulsado, para así poder consultar cualquier tipo de información con respecto al mismo o provocar que este tenga algún cambio en su comportamiento o propiedades.

Por ejemplo, si nosotros cambiamos el valor en un campo de texto, el controlador recibe dicho cambio pero no puede hacer nada porque no hay una acción que le haga darse cuenta de ello. Por eso, la forma natural de interactuar es que yo escriba un contenido en un campo y pulse un botón. Eso dispara un método en el controlador que recuperará el nuevo valor del campo a través de su correspondiente propiedad outlet, y entonces este actualizará el modelo si es necesario.

Eventos de interfaz, delegaciones

Pero este canal de comunicación es muy limitado y el controlador solo puede estar informado de los cambios a partir de una acción concreta, como ya hemos indicado. Pero ¿y si la acción es algo que el usuario ha hecho en la interfaz? Como por ejemplo hacer scroll en una tabla o ha tocado una fila concreta de la misma. Son acciones que el usuario hace con objetos que no reaccionan ni crean acciones. Son acciones que interactúan con la funcionalidad de los elementos. Es decir, no son acciones, son simples interacciones pero que puede que la vista necesite saber o quiera preguntar qué hacer en caso que estos sucedan.

Estas interacciones se dividen en tres tipos: debería poder (should), va a hacerse (will) y se ha hecho (did). ¿Debería permitir editar un campo de texto concreto? ¿Debería permitir hacer scroll vertical u horizontal? Son preguntas que la vista necesita (en ocasiones) poder contestar para configurar su propia interactividad y ella sola no sabe si debe o no hacerlo. Eso representa los eventos should. Otras veces la interfaz nos avisa que va a comenzarse a editar un campo de texto o va a terminarse de hacer dicha edición. Estos son eventos will que saltan antes que la acción se realice. Y por último, también puede ser que nos informe que se ha cambiado un dato en algún sitio, que se ha realizado algo concreto. Para eso tenemos los eventos did.

Todos ellos son preguntas que la vista hace al controlador: ¿debo dejar que se haga scroll vertical? ¿hago algo si van a empezar a editar un campo? ¿qué hago si se ha modificado el valor de un campo? Preguntas que se realizan por la interactividad del usuario en la vista y que el controlador responde mediante delegaciones, el cual es un patrón de diseño de por sí. Y son delegaciones, porque la vista delega la responsabilidad de qué ha de hacer en cada caso al controlador.

Las delegaciones funcionan a través de protocolos, de forma que cuando nuestra clase se conforma a uno expone una serie de definiciones (especificaciones) que aquel que se conforma a ellos se compromete a implementar. Técnicamente se dice que está conformado.

Fuentes de datos, delegaciones

La vista no almacena datos ni sabe qué son. Solo los muestra. Los datos están en el modelo y es el controlador el que ha de pedirlos. Pero la forma en que estos se proporcionan es a través, igualmente, de delegaciones. En este caso, delegaciones de fuente de datos que solicitan la información a mostrar.

Imaginemos una vista de tabla con celdas. La vista, cada vez que muestra una celda (va a dibujarla) llama a una función del controlador a través de una delegación donde le dice: dame el contenido para la celda de la fila 1 (por ejemplo). Esto representa las fuentes de datos y es el controlador el que ha de solicitar esta información al modelo y proveerlo como resultado de la función delegada invocada.

A estas delegaciones las llamados fuentes de datos o data sources. Y hemos de entender que siempre es el controlador el que interpreta y da formato a la información del modelo para que se muestre en la vista tal como se definió a tal efecto.

Comunicación del modelo al controlador

El controlador pregunta siempre al modelo. Pero como el modelo es independiente de cualquier tipo de interfaz porque solo tiene datos y procesos, este no se comunica con el controlador. Es decir, todas las comunicaciones parten siempre del controlador que le pide cosas al modelo para que le devuelva algo o cuando el controlador le dice al modelo que actualice o inserte algún nuevo dato o inicie algún proceso de los que tenga. Pero el modelo como tal no tiene un canal de comunicación para pedirle al controlador que haga cosas. La comunicación es solo en una vía: del controlador al modelo.

No obstante, sí es cierto que el modelo a veces tiene eventos. Pero veamos un caso concreto. Estoy en Twitter y desplazo en la parte superior la interfaz para provocar el refresco de los datos. La vista llama al controlador a través de una delegación de tipo did y este le pide los datos al modelo que se conecta a internet. Una vez el modelo recibe la respuesta desde Twitter, el controlador refresca la tabla provocando un redibujado de la misma que, a su vez, provocará que la vista le vuelva a pedir la información de cada fila de la tabla a través de la delegación de fuente de datos al controlador.

Pero, aquí nos falta algo. Porque recuperar la información de Twitter es un proceso asíncrono. Un proceso cuya respuesta no es inmediata, sino un proceso que provoca una llamada y que devolverá un resultado más adelante. Un proceso que rompe el ciclo normal de llamada y respuesta (el modo síncrono). ¿Cómo entonces el modelo puede informar al controlador que ya tiene los datos que espera si no puede iniciar llamada alguna? ¿Cómo hace el modelo para provocar que el controlador sepa que algo ha pasado y que este reaccione y le pregunte? Para ello usamos otro patrón en sí mismo compuesto por notificaciones y lo que conocemos como KVO o Key-Value Observing.

Las notificaciones y los observadores de valores clave, son eventos que se disparan en el modelo cuando pasa algo. Una notificación que se propaga por todo el sistema y que cualquiera que tenga una radio sintonizada en la misma frecuencia oirá. Para ello creamos un observador en el controlador con un nombre determinado asociado a la ejecución de un método. Si recibimos una notificación que se llama "nuevosDatos", lo que hacemos es lanzar una función de nuestro controlador asociada a ese observador. De esta forma, en el momento que alguien lance esa notificación (sea donde sea en el programa) nuestro observador (que tiene “sintonizado” el canal para esa llamada) se activará y ejecutará la función que tenga definida dicho observador.

En este caso, el modelo es el que genera la notificación y el controlador el que tiene “sintonizada la radio” (configurado el observador) para que cuando esta se lance se ejecute el método correspondiente que provoque el normal flujo de petición de información del controlador hacia el modelo.

Resumiendo

Son muchos conceptos los que hemos presentado así que vamos a sintetizar:

  • Modelo: es donde están nuestros datos y la lógica de los mismos. Lo que es nuestro programa. Todo independiente de cómo serán mostrados. Bases de datos, procesos que convierten datos en otra cosa, que los validan, que los almacenan en las bases de datos u otro lugar donde persistimos los datos… Eso es nuestro modelo.
  • Vista: la interfaz de usuario. Los elementos que conforman cómo se mostrará la información en pantalla. Etiquetas, botones, campos, interruptores… igualmente interfaces complejos como vistas de tabla, de scroll, de colección… Son solo huecos donde poner cosas y no saben nada de los datos ni les importan. Ellos solo ponen cosas ahí pero nunca saben ni sabrán qué representa (ni les incumbe). De esta forma, si cambiamos completamente el modelo, dará igual. La vista seguirá sirviendo si los huecos siguen siendo válidos para mostrar la información.
  • Controlador: el que se encarga de controlar la app. Llama al modelo cuando necesita datos y este se los proporciona y le dice cuando ha de actualizar algún dato. Igualmente, establece las asociaciones entre elementos de código y elementos en la vista mediante outlets con el objeto que la vista tenga información que mostrar.
  • Outlets: las conexiones entre la vista y el controlador que asocian propiedades de la clase del controlador a objetos de la vista para poder proporcionar la información a mostrar.
  • Delegaciones: eventos que genera la vista para informar al controlador que algo ha pasado y preguntarle qué hacer en dicho caso.
  • Fuentes de datos: los datos que la vista le pide al controlador cuando necesita mostrar algo en una interfaz completa como una vista de tabla o de colección.
  • Notificaciones: eventos que el modelo genera cuando ha de informar al controlador de algún cambio, mediante el lanzamiento de una llamada (una notificación) que es escuchada por un observador que genera una llamada a una función.

Esto es la esencia de todo el desarrollo actual en entornos Apple. Esto que hemos explicado es como hoy día se trabaja de manera nativa, evolución o herencia de aquel primer Interface Builder que NeXT lanzó en 1988 y que revolucionó la forma en que se entendía y trabajaba en el mundo del desarrollo. Semilla que creó el Xerox PARC en el año 1979.

Es probable que ya conozcáis el modelo, que hayáis trabajado con él, pero seguro que muchos no conocíais el concepto teórico hasta este nivel. Seguro que muchos habéis dicho: “¡Anda, claro!”. Como he dicho muchas veces, no es lo mismo trabajar y descubrir las cosas sobre la marcha mientras trabajas, a leerlo todo ordenado y explicado en un mismo sitio, siguiendo una progresión que te haga unir o asociar conceptos.

Si os ha parecido bien la explicación y os ha ayudado, no dejéis de compartirlo en redes sociales y recomendarlo. Y si queréis saber más sobre el desarrollo de apps, iniciaros en el maravilloso del desarrollo móvil o coger todo ese barullo mental que tenéis y sintetizarlo, poniendo cada cosa en su sitio, no dejéis de pasar por Apple Coding Academy y apuntaros a algunos de nuestros cursos. Nuestra filosofía es la que habéis leído en este artículo: entender los conceptos desde la base para trabajar a partir de ahí. Conocer los conceptos para comprender.

Banner Apple Coding Academy

Un saludo y Good Apple Coding.

Acerca de Julio César Fernández

Analista, consultor y periodista tecnológico, desarrollador, empresario, productor audiovisual, actor de doblaje e ingeniero de vídeo y audio.

Otras recomendaciones

Prototipos Unit Testing TDD

Lecciones por prototipos (II): test unitarios (XCTest y TDD)

Una de las cosas que normalmente se perciben más complejas en el desarrollo en cualquier …