A lo largo de tu carrera como desarrollador de aplicaciones para iOS te encontrarás utilizando muchos patrones de diseño como por ejemplo Singleton, MVC, Decorator, Observer; pero uno de los más importantes y utilizados es Delegation. Antes de entrar en detalles, ¿qué es un patrón de diseño?
Patrones de diseño
No vamos a profundizar en este post acerca de los patrones de diseño pues hay muchos artículos y libros sobre el tema, solamente veremos de que se trata y pueden consultar luego al respecto.
El concepto de patrones de diseño fue introducido en 1994 en el libro “Design Patterns: Elements of Reusable Object-Oriented Software” de los autores Erich Gamma, Ralph Johnson, Richard Helm, John Vlissides, el cual trata sobre temas de Programación Orientada a Objetos y patrones de diseño y desde entonces ha influenciado mucho en la ingeniería de software; este colectivo de autores es conocido por “Gang of Four (GoF)”.
Los patrones de diseño representan buenas prácticas utilizadas por desarrolladores de software, soluciones generales a problemas que se presentan durante el desarrollo de software. Estas soluciones fueron obtenidas mediante un proceso de ensayo y rectificación de errores de muchos desarrolladores de software a lo largo de un período considerable de tiempo.
Delegation
Este patrón brinda la posibilidad a una clase o estructura de entregar (o delegar) alguna de sus responsabilidades a una instacia de otro tipo.
La implementación de este patrón esta basada en la definición de un protocolo que encapsule las responsabilidades que serán delegadas de modo tal que se garantiza que el tipo que implemente este protocolo (conocido como delegado) proveerá las funcionalidades que han sido delegadas.
Veamos un ejemplo en la próxima sección.
Implementando el patrón
Vamos a utilizar como ejemplo la implementación sencilla de una clase CuentaDeAhorros la cual va a permitir a sus usuarios modificar su saldo.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CuentaDeAhorros { | |
var saldo: Double = 0 | |
} |
Como pueden ver es una clase muy sencilla, la cual solo tiene una propiedad saldo, con lo cual es suficiente para el ejemplo que queremos mostrar, en una aplicación real probablemente saldo sería un atributo privado y tendríamos métodos para extraer dinero, depositar dinero, etc.
Si queremos crear una cuenta de ahorros y ponerle como saldo $5 solo tenemos que hacer lo siguiente:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let cuenta = CuentaDeAhorros() | |
cuenta.saldo = 5 |
Queremos adicionar una nueva funcionalidad la cual consiste en poder notificar cuando se detecte un cambio en una cuenta de ahorros y para ello vamos a utilizar la delegación de modo que la clase CuentaDeAhorros delegue esa responsabilidad.
Primeramente definamos el protocolo que va a encapsular las responsabilidades a delegar, la cual sería “notificar cualquier cambio de saldo que ocurra en la cuenta”:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protocol CuentaDeAhorrosDelegate { | |
func notificarCambioDeSaldo(saldoActual: Double, nuevoSaldo: Double) | |
} |
Al protocolo lo llamaremos CuentaDeAhorrosDelegate porque son responsabilidades de la cuenta de ahorros que queremos delegar y queremos agruparlas de modo que el delegado pueda implementarlas a través de este protocolo.
Es importante destacar que en un protocolo deben estar definidos métodos, propiedades, etc. que respondan a una misma tarea, en este ejemplo solo tendremos un método para notificar cambios en el saldo de la cuenta, si mañana queremos adicionar otro método relacionado con cambios en el saldo de la cuenta podemos ponerlo en este protocolo pero si en cambio, queremos adicionar un método que tenga que ver con otra tarea, es recomendado definir otro protocolo.
Es hora de definir nuestro delegado, el cual va a implementar el protocolo CuentaDeAhorrosDelegate:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Notificador : CuentaDeAhorrosDelegate { | |
func notificarCambioDeSaldo(saldoActual: Double, nuevoSaldo: Double) { | |
print("El saldo actual es $(saldoActual) y el nuevo saldo será $(nuevoSaldo)") | |
} | |
} |
- Definimos una clase llamada Notificador pues su tarea será notificar los cambios de saldos.
- La hacemos implementar el protocolo CuentaDeAhorrosDelegate.
- Colocamos la implementación del método func notificarCambioDeSaldo(saldoActual: Double, nuevoSaldo: Double). En este caso lo que va a hacer este método es imprimir cuál es el saldo actual y cuál va a ser el nuevo saldo. En una aplicación real posiblemente queremos registrar esto en una base de datos, en un archivo, etc.
El próximo paso es modificar la clase CuentaDeAhorros para que utilice al delegado para delegar la funcionalidad de notificar cuando se realice un cambio en el saldo de la cuenta. Entonces realizamos las siguiente modificaciones:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CuentaDeAhorros { | |
var delegate: CuentaDeAhorrosDelegate? | |
var saldo: Double = 0 { | |
willSet(nuevoSaldo) { | |
delegate?.notificarCambioDeSaldo(saldo, nuevoSaldo: nuevoSaldo) | |
} | |
} | |
} |
Las modificaciones realizadas fueron las siguientes:
- Adicionamos un nueva propiedad llamada delegate que es de tipo CuentaDeAhorrosDelegate el cual es el protocolo que definimos para representar las tareas a delegar; utilizamos el protocolo y no directamente el tipo Notificador porque de ese modo podemos utilizar cualquier tipo que en el futuro implemente CuentaDeAhorrosDelegate, en otras palabras podemos decir que a través de la propiedad delegate vamos a inyectar a nuestro delegado.
- Modificamos la implementación de la propiedad saldo para adicionar un observador, los observadores de propiedades (property observers) es una funcionalidad de Swift para las propiedades, estos observadores observan y responden a cambios en los valores de las propiedades, pueden ver más en la documentación de Apple. Para este caso vamos a utilizar willSet el cual nos va a notificar cuando vaya a ocurrir un cambio en el valor de la propiedad antes de que ocurra y además permite el uso de un argumento donde viene el futuro valor de la propiedad, en este caso nuevoSaldo.
- Finalmente, en la implementación del observador, llamamos al método notificarCambioDeSaldo de nuestro delegado pasándole el saldo actual y el nuevo saldo.
Lo último que falta es registrar un delegado a nuestra instancia de CuentaDeAhorros, y lo podemos hacer de la siguiente manera:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let notificador = Notificador() | |
let cuenta = CuentaDeAhorros() | |
cuenta.delegate = notificador | |
cuenta.saldo = 5 | |
cuenta.saldo = 20 |
- Primeramente creamos una instancia de Notificador llamada notificador.
- Creamos nuestra instancia de CuentaDeAhorros llamada cuenta.
- “Inyectamos” nuestro delegado notificador en nuestra instancia de CuentaDeAhorros a través de su propiedad delegate.
- En la cuarta línea cambiamos el saldo y le asignamos 5, al hacer esto se va a ejecutar el observador de saldo willSet y va a llamar al método notificarCambioDeSaldo en nuestro delegado, como nuestro delegado es notificador que es de tipo Notificador, lo que va a suceder es que se va a imprimir el siguiente texto “El saldo actual es $0.0 y el nuevo saldo será $5.0\n”.
- En la quita línea volvemos a cambiar el saldo, esta vez a 20 y va a suceder lo mismo pero en esta ocasión como el saldo actual era 5, el texto que se va a imprimir es “El saldo actual es $5.0 y el nuevo saldo será $20.0\n”.
Como decíamos al principio, es importante aprender como funciona este patrón pues en el desarrollo de aplicaciones para iOS se encontrarán implementando y utilizando este patrón una y otra vez.
Muchas gracias, he entendido de una manera el patron delegate, espero pudieras sacar mas acerca de patrones de diseño como Facade, Observer y los mas comunes para iOS.
Que bueno que te fue útil, voy a poner tu sugerencia en mi lista para nuevos posts. Gracias!
Realmente muy bueno, y muy claro tu ejemplo. Enhorabuena.
Gracias, me alegra que le haya gustado.