Home » Análisis » Programación orientada a protocolos, la evolución en Swift 2
Programación Orientada a Protocolos

Programación orientada a protocolos, la evolución en Swift 2

C++, Objective-C, C#, Java… todos estos lenguajes tienen en común un uso fundacional del lenguaje que los define en sí mismos: la programación orientada a objetos. Un objeto es una entidad independiente que se construye a base de propiedades (variables o constantes) y funciones (o métodos). Una entidad que dentro de su independencia puede tener relación con otras a través de vínculos padre e hijo y construir un esquema de objetos con comportamiento común pero datos diferentes que construyen cualquier aplicación, juego o programa que creemos. Es la esencia de estos lenguajes, su arquitectura y, lógicamente, también es parte de Swift.

Pero, ¿podríamos ir más allá? ¿habría alguna forma de conseguir que un lenguaje evolucionara para permitirnos mayor flexibilidad a la hora de construir instrucciones o métodos que nos ayudaran a realizar tareas? Esa es la pregunta que Apple se ha hecho para Swift 2 y la respuesta es la programación orientada a protocolos.

Protocolos

¿Qué es un protocolo? Son plantillas de especificación que definimos para crear unas reglas determinadas que queremos que se cumplan en una implementación que podemos usar en varias clases, structs o incluso enumeraciones. Definen requisitos a implementar utilizando solamente las cabeceras de las propiedades o métodos que queremos se incluyan obligatoriamente para, como suele denominarse, conformar con un protocolo.

Los protocolos además nos permiten agrupar diferentes tipos de datos a través de características afines o comunes a todos ellos, proporcionando una misma funcionalidad determinada a todo aquel tipo de dato u objeto que cumpla con el protocolo con el que se conforma.

Si podemos imaginar una serie de botones, campos de texto y etiquetas, podíamos crear una única función a través de un protocolo que coloreara el tipo de letra utilizado. Cada uno de estos objetos necesitará una implementación diferente para hacer esto, pero si hacemos una única cabecera de función, la incluimos en un protocolo, y hacemos que estos objetos tengan que cumplir con él, podemos garantizar (aunque cada objeto lo haga a su manera y tenga su propia implementación que habrá que crear) que tendremos una única llamada que va a proporcionarnos una misma funcionalidad: cambiar el color de una tipografía. Eso, en esencia, es un protocolo.

El por qué de un nuevo modelo

La programación orientada a objetos no es perfecta y tiene sus problemas. El primero de ellos es que son objetos que viven por referencia lo que puede ser útil en algunas circunstancias, pero en otras nos obligan a crear copias para poder tener diferentes versiones de un mismo objeto en caso que queramos varios personajes de un juego con las mismas características o varios componentes de un mismo tipo en una app pero con diferencias de uno a otro (como fichas de datos, fotografías, notas…). Además, dos variables que apuntan a un mismo objeto, cambiarán el mismo por igual y no por separado debido al modelo que usamos.

Otro problema que podríamos plantear es que la infraestructura de la herencia nos obliga a aceptar todos los componentes del padre en el hijo y siempre tendremos un único padre para cada hijo, por lo que estamos limitados a la hora de crear estructuras más complejas y anidadas.

En el caso de Swift, uno de los grandes problemas que se plantean con la programación orientada a objetos es cómo se pierden los tipos cuando trabajamos con diferentes tipos de clases. Podemos tener varios hijos de un mismo padre y si queremos una función que actúe con todos ellos, debemos de usar la clase del padre como tipo para la referencia del parámetro y eso implica directamente la pérdida de la referencia y de los métodos o propiedades propios del hijo en cuestión, lo que nos obliga a detectar y hacer un downcast para restaurar el tipo de dato original y con ello poder volver a acceder a las propiedades o métodos que el hijo en sí tiene.

Y por último, el modelo orientado a objetos no soporta tipos de datos por valor, como cualquier dato básico, una enumeración o un struct.

Un modelo de abstracción más eficiente: orientación a protocolos

La idea es buscar un modelo de abstracción más eficiente que soporte tipos de datos por valor, que no obligue a modelos de instanciación de datos ni de inicialización previa, así como que proporcione un modelo más claro a la hora de saber cómo implementar. Cuando nosotros definimos un struct no tenemos que darle valores a sus propiedades cuando las estamos creando, solo hay que dárselas cuando lo creamos en una variable. Sin embargo, una clase nos obliga a inicializar todas sus propiedades en el momento en que estamos creándola y no cuando se instancia. Esto es una facilidad intrínseca a la hora de implementar un modelo de abstracción.

Por eso Apple ha convertido Swift en un lenguaje con un modelo de abstracción más amplio, compatible con la orientación a objetos y muy eficiente en él, pero su base es la orientación a protocolos: Swift es el primer lenguaje que usa este modelo de abstracción.

¿En qué consiste? Consiste en buscar otra forma de acceder a la abstracción de métodos y propiedades, pero a través de tipos de datos por valor (que son copiados por el sistema en cada nueva asignación) y que no funcionan por referencia. El manejo de este tipo de datos es más eficiente en memoria en cuanto a que evitamos ciclos de retención, referencias perdidas, etc. Dato que sirve y está en ámbito, está, dato que no, se borra.

Para conseguir esto se sustituyen clases por structs, que en Swift son idénticos en todos los aspectos salvo en tres: las clases son datos por referencia (la variable o constante guarda la dirección de memoria donde está el objeto y cualquier nueva asignación solo copia dicha dirección y aumenta las referencias a este objeto) y los structs son tipos de datos por valor (con entidad única y que residen como tales en la propia variable o constante). Además una clase soporta herencia, pero un struct no y como hemos dicho, la clase obliga a inicializar sus propiedades cuando se crea su implementación pero un struct no.

¿Cómo accedemos entonces a la abstracción a partir de structs si no soporta herencia? A través de protocolos, donde solo definimos las cabeceras de las funciones o propiedades calculadas que nuestro struct debe tener para conformar el protocolo, pero no incluye la implementación o el cuerpo del código. De esta forma, podemos crear cualquier estructura que conforme el protocolo determinado y proporcionar un código diferente para cada método o propiedad almacenada dentro de cada struct que creemos, como si hiciéramos un override de un método del padre en el hijo pero teniendo en cuenta que aquí todos funcionan a un mismo nivel.

Además, estos protocolos también podemos usarlos como extensiones de tipos del propio sistema y conseguir una funcionalidad específica.

Las extensiones de protocolos: la varita mágica

Los protocolos nos permiten expresar la definición de aquellos métodos o propiedades calculadas que han de ser incluidas para conformarse con el mismo. Pero solo las cabeceras, no la implementación. Esto es un problema porque si para implementar este nuevo tipo de orientación hemos de crear todas y cada una de las implementaciones que vayamos a usar en todos y cada uno de los structs que creemos, no resulta muy eficiente.

Por eso Apple ha agregado a Swift 2 las extensiones de protocolos, que no solo nos permiten incluir qué métodos o propiedades han de incluirse, si no también nos permiten generar la implementación por defecto que un método o propiedad usará cuando esté conformada con dicho protocolo. Sí, has leído bien: la implementación, es decir, el cuerpo de código de la función y su funcionalidad por defecto que, en caso que queramos modificarla, podremos hacerlo sin problema en aquellos structs donde nos pueda ser necesario.

Esto nos proporciona la forma perfecta para darle uso a este nuevo modelo de orientación: creamos los protocolos necesarios que vayan a formar parte de la especificación de cualquiera de nuestras nuevas estructuras, creamos los elementos de la aplicación mediante structs y luego creamos extensiones a los protocolos que incorporen la funcionalidad estándar que cualquiera de los structs va a utilizar para que no tengamos que crear una implementación para cada uno de los structs que creemos, cuando en algunos casos esta será repetida.

Y lo realmente útil es que aquellas funciones que incorporemos con implementación, no será necesario incluirlas para que nuestro struct conforme el protocolo, si no que será como si fueran opcionales. Y siempre podremos proporcionar nuestra propia funcionalidad específica a dichas funciones, sobrescribiendo aquella que den por defecto.

También Swift 2 pone a nuestra disposición una nueva implementación de genéricos que nos permite, mediante una instrucción where, acotar el ámbito de una extensión de protocolo solo a los tipos de datos que cumplan con alguno de los protocolos del sistema como el de la igualdad (Equatable), la comparación (Comparable) o cualquier otro.

Primero la teoría, luego la práctica

Este concepto es muy importante dentro de Swift 2 y hemos querido dividirlo. Lo que tenéis aquí es una explicación lo más clara posible y si estáis familiarizados con el concepto de los protocolos, las extensiones y los structs, entenderéis la diferencia y lo que hemos explicado.

No he querido usar los ejemplos que Apple da en su libro o los que se usan en el vídeo de explicación de esta nueva forma de desarrollar, porque no me han parecido suficientemente claros y quiero buscar mis propios ejemplos mucho más cercanos, que veremos en detenimiento en otro artículo.

Por ahora, lo importante es que sepáis que estamos ante una nueva forma de confrontar la abstracción, diferente a la programación orientada a objetos y que Swift es el primer lenguaje que la incorpora. Una forma innovadora y muy práctica pero que nos obliga a cambiar la forma de pensar.

Hasta el próximo capítulo, echad un vistazo a todos estos conceptos y cuando lleguen los ejemplos lo veréis mucho más claro. Trabajad, explorar 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

Diseñando para el iPhone X

iPhone X (léase 10). El nuevo y más deseado dispositivo de Apple, no carente de …