Home » Cursos » Programación Orientada a Protocolos
Banner Programación Orientada a Protocolos

Programación Orientada a Protocolos

Hace ya algún tiempo hablamos sobre la programación orientada a objetos, pero visto desde un punto de vista teórico. En este artículo hacíamos una aproximación a este nuevo mecanismo de abstracción, intentando explicar el por qué del mismo.

Pero para llegar a entenderlo de una forma práctica, primero tenemos que aprender los conceptos básicos que dan forma a este. Al igual que la orientación a objetos nos obliga a conocer las clases, la herencia, la sobrecarga (o polimorfismo) y algunos conceptos más, la orientación a protocolos tiene como “ingredientes” principales tres que podemos repasar en cada uno de los enlaces: los protocolos, los structs y las extensiones.

Con todos estos ingredientes entendidos, podemos ver cómo aplicar toda esta teoría. Y en este caso, vamos a hacerlo siguiendo parte del guión de la charla-clase que impartí en el Madrid International Lab sobre este tema, el pasado 6 de abril de 2016, del que podéis ver una de las imágenes gracias a nuestro lector @rgomezmu.


Animales orientados a objetos

Uno de los ejemplos que usé en la charla fue con animales, ¿cómo tendríamos que hacer una aproximación a través de objetos sobre unos animales que tuvieran una serie de características y comportamientos? Lo normal es que hiciéramos algo parecido a esto:

Si analizamos esta clase, vemos que tenemos tres propiedades: sonido de tipo String, numeropatas de tipo Int y alimentacion que es un opcional de la enumeración Comida que hemos definido en la propia clase. Y como clase, tenemos dos opciones para crear e inicializarla. Si queremos que se pueda crear con una instanciación vacía, hemos de asegurarnos que todas las propiedades almacenadas incluidas (las tres que hemos metido) sean u opcionales (que se inicializan solas a nil) o propiedades que al declararlas se inicialicen con algún valor (como una cadena vacía o un valor por defecto).

En el ejemplo, hemos preferido no inicializar las propiedades sonido y numeropatas, simplemente declarándolas, lo que nos obliga en ese caso a crear un inicializador designado (de manera obligatoria) que recoja como parámetros los dos valores y los use para inicializar estas propiedades. Podríamos usar el inicializador vacío si damos valor a estas dos propiedades en dicho init, pero tiene poco sentido.

Por lo tanto, lo que tenemos ahora es una clase animal que nos va a permitir crear tantos animales como queramos.

Si inicializamos un leon, como es el caso, tenemos que invocar al inicializador que hemos creado y listo. Pero, ¿qué pasa si queremos tener un animal que tenga un valor añadido? Tendríamos que usar herencia.

En este caso, con dicha nueva clase Perro, lo que estamos haciendo es incorporar una nueva propiedad almacenada raza que toma sus valores desde una enumeración Raza que hemos creado dentro de la propia definición de la clase. Pero cuando queremos crear un nuevo perro, resulta que tenemos que usar el inicializador designado de su padre y darle valores a sonido y numeropatas.

Aquí nos encontramos varios problemas. El primero es que como vemos, tenemos que heredar en las clases hijas todo lo que el padre lleve y atenernos a sus comportamientos (como inicializadores designados o incluso obligatorios (de tipo required). Y además, tenemos otro problemas más: ¿qué pasaría si quisiéramos crear un objeto jirafa de tipo Animal, sabiendo que las jirafas no emiten sonido alguno?. Nos estaríamos encontrando con un caso de utilización no adecuada, ya que hay un propiedad en nuestra clase que no vamos a usar.

No tiene mucho sentido que hagamos esto, lo cual nos indica que para este caso, tal vez la orientación a objetos no sea la más apropiada.

Animales orientados a protocolos

La orientación a protocolos tiene tres ingredientes principales, como ya hemos dicho: protocolos, structs y extensiones de protocolos. Son los elementos esenciales para pensar en nuestra abstracción que tiene que estar basada en un factor clave: la identidad. Cada identidad estará compuesta de diferentes características y comportamientos (propiedades y métodos) y a su vez estas identidades suman. Pero dicha suma se sucede a un solo nivel: mientras nuestro Perro en orientación a objetos dependía de Animal, aquí Perro englobaría sólo aquello que lo identifica y no tendría por qué incluir las características de Animal. Todo sucede en un único nivel y donde sumamos identidades a un tipo único.

Lo que tenemos que pensar, es que aquello que hemos definido como propiedades o métodos en una clase, ahora formará parte de un protocolo: de unos requisitos a los que obligar a conformar el struct que contendrá las implementaciones e inicializaciones de nuestras especificaciones.

De esta forma, hemos establecido las condiciones que queremos que tenga cualquier struct que queramos se conforme a nuestro protocolo. Si nos fijamos, hemos creado sonido, numeroPatas y alimentacion como de tipo var con sus get o set, es decir, que están definidas como propiedades calculadas (no como almacenadas). Esto es debido a que los protocolos SOLO pueden definir propiedades calculadas, nunca almacenadas… en teoría. Porque para poder permitir la abstracción que hacemos, en realidad sí podemos tratarlas como almacenadas y el compilador se encargará de la consiguiente transformación.

Ahora solo tenemos que crear un struct que se conforme a dicho protocolo y listo. Ya tenemos una implementación orientada a protocolos básica.

Pero claro, aquí no estamos viendo nada que aporte realmente a todo lo que hemos dicho. Veamos el caso donde sí. ¿Qué pasaba con nuestra jirafa? Pues que no hablaba, así que vamos a crear un protocolo nuevo que englobe las características y comportamientos de una identidad que nos diga que algo habla: un protocolo Hablador.

Para ello ponemos juntas a la propiedad sonido y al método hablar. Y obviamente, tenemos que quitarlo del protocolo Animal porque ya no nos es necesario: hemos dividido un solo protocolo en dos, usando como criterio la identidad: un animal y un hablador.

Ahora, cuando creemos el león, tenemos que cambiar porque la cabecera pasará del actual struct Leon:Animal a una suma de ambas identidades, separadas por comas: struct Leon:Animal, Hablador. ¿Qué pasa entonces con nuestra jirafa?

Pasa que ya no tenemos que poner sonido porque no forma parte del protocolo al que tiene que conformarse, al igual que pasa con el método hablar. Si ahora queremos crear al perro, haríamos un struct que englobara todos los protocolos que hemos creado.

Estamos hablando que esto sería una suma de requisitos, la unión de varios protocolos que forman una identidad.

Extensiones de protocolos

El último ingrediente de nuestro plato son las extensiones de protocolos, porque ahora mismo tenemos un problema de base: nuestros structs Leon y Perro usan la misma función hablar.

Si cada vez que queremos crear un struct nuevo tenemos que hacer un copy+paste del mismo código (porque normalmente vamos a tener esa misma funcionalidad) la solución es poco práctica. Así que nos creamos una extensión del protocolo Hablador que nos permita especificar una implementación por defecto para nuestra función y, por lo tanto, no tener que incluir su código cada vez que creemos un nuevo struct, siempre y cuando no necesitemos una funcionalidad específica.

Ahora, nuestros structs de Leon o Perro pueden prescindir de esa parte del código:

Esta la conjunción perfecta de protocolos, structs y extensiones, que nos permiten abordar un problema de una forma diferente. Y es importante entender que si, para un caso concreto, quisiéramos otra implementación diferente para el método hablar, solo habría que incluirlo en el struct que quisiéramos y listo: sería como hacer sobrecarga de un método por herencia en orientación a objetos.

Grandes ventajas

Usar este tipo de abstracción nos aporta claras ventajas como eliminar de un plumazo los problemas con las referencias de los objetos o su identidad de cara a los castings (un struct siempre es de un mismo tipo). Perro, Leon o Jirafa son tipos únicos y no dependen de ningún otro, por lo que si trabajamos con ellos nunca tendremos problemas de casting. Solo en el caso que quisiéramos agrupar varios de estos tipos en un mismo array (por ejemplo) tendríamos que acudir a los as para resolver los problemas de identidad, pero esto por otro lado es obvio e intrínseco a todo el lenguaje (es lo mismo que pasa si juntamos en un array String e Int, por ejemplo, que son también tipos de dato por valor).

La orientación a protocolos es otra forma de abordar un problema, mucho más eficiente en muchos casos y en otros complementaria a la orientación a objetos. Junto a esta lección podéis conseguir los contenidos que usé el pasado 6 de abril en la mencionada charla para NSCoder Night Madrid, donde tenéis el keynote que usé para la exposición y el playground donde tenéis el código de ejemplo que hemos comentado, así como otro sobre la creación de objetos SKShapeNode a partir de una implementación orientada a protocolos.

Estudiadlos, probad con ellos, preguntad cualquier duda sin miedo y Good Apple Coding.

Presentación Keynote: Acceder en slideshare.net
Playground con código de ejemplo OPP-NSCoder.playground: Acceder en github.com

Vídeo de la charla de NSCoder Night Madrid del 6 abril, grabada por NSCoder Night Madrid.

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

Lecciones Prototipos (I): UITableView

Lecciones por prototipos (I): Vistas de tabla (UITableView)

Primera lección por prototipos, un nuevo e innovador contenido. A veces, lo normal es que nos perdamos sin terminar de entender qué es o cómo funcionan los componentes que forman parte de una app o un juego. Para este caso hemos creado las lecciones por prototipos. Una exploración básica de conceptos esenciales a través de prototipos en Playground que podemos probar con Swift Playgrounds en el iPad o con Xcode 8. En esta primera lección abordamos las UITableView (vistas de tabla). Un elemento esencial en la mayoría de apps de iOS que muchas veces no es entendido desde su base y por lo tanto, provoca un mal uso de las mismas.

  • xtramike

    Hola Julio,

    muy interesante. No te parece que si hay algún animal que no tenga la capacidad de hablar, no debería ser parte de la clase y que se definiera ese comportamiento en cada uno de sus hijos?

    De esa forma, no se daría nunca el caso de hacer un init con un valor vacío.

    • Sería una solución, pero requeriría de más niveles de herencia. Existen muchas posibles soluciones en orientación a objetos pero también por protocolos, lo que ejemplifica diferentes posibles soluciones. La que comentas es una válida, también. Gracias por el comentario.

  • Carlos Zamora Manzur

    Estimado, un detalle en la línea 5: “Al igual que la orientación a protocolos nos obliga a conocer las clases” no debiera decir “orientación a objetos”? porque más adelante vuelves a mencionar protocolos, es decir, comparas orientación a protocolos con orientación a protocolos! para que revises y corrijas, saludos!

    • Hola Carlos,
      Tienes razón. Lapsus de teclado. Gracias por hacerlo notar y ya está corregido.
      Un saludo.

      • Carlos Zamora Manzur

        super! y gracias a ti por el artículo!