¿Sabes qué son los patrones de diseño Swift? Lanzado en el año 2014 Swift es conocido como el lenguaje de programación propio de Apple. Y, hoy en día se ha convertido en una herramienta poderosa que permite a los desarrolladores producir aplicaciones versátiles para diversos sistemas operativos
Precisamente por ser un instrumento nuevo, muchos desarrolladores aún desconocen qué patrones de diseño en Swift deberían utilizar y la manera de ejecutarlos. Tener la posibilidad de utilizar un patrón de diseño es un requisito previo para poder generar aplicaciones funcionales, de calidad y 100% seguras.
En este post quiero mostrarte a fondo los patrones de diseño más usados en Swift y analizar los diferentes enfoques para solucionar inconvenientes usuales en el desarrollo móvil con ellos. Sigue conmigo y no te pierdas de cada detalle.
¿Cuáles son los patrones de diseño Swift?
En general, un patrón de diseño de programa representa una solución a un problema específico que se podría confrontar al diseñar la arquitectura de una aplicación.
No obstante, a diferencia de los servicios preparados para utilizar o los documentos de código abierto, no es posible pegar un patrón de diseño en una aplicación ya que este no es un fragmento de código.
Los patrones de diseño Swift son plantillas que ayudan a solucionar un problema y explican cómo redactar un código, esto depende de los ajustes que se necesitan en una determinada plantilla.
¿Por qué usarlos?
Cuando se tienen problemas de programación, no hay nada mejor que contar con la ayuda apropiada. En este sentido, el uso de patrones de diseño Swift nos permiten:
Resoluciones probadas
Ya no perderás tiempo y esfuerzo en la búsqueda de una salida, los patrones de diseño tipo Swift brindan la solución que buscas y te indican la manera de implementarla.
Unificación de código
Los patrones de diseño te otorgan resoluciones típicas que fueron probadas para identificar problemas y errores, lo cual ayuda a cometer menos errores al diseñar la arquitectura de tu aplicación.
Vocabulario común
En vez de proveer explicaciones detalladas referente a cómo solucionar este o ese problema de desarrollo de programa, sencillamente puede señalar qué patrón de diseño usó y otros desarrolladores comprenderán rápido qué soluciones implementó.
Tipos de patrones
Los patrones de diseño Swift, como hemos dicho, son instrumentos de lenguaje que permiten producir diversas aplicaciones para sistemas operativos. En este caso, se distinguen tres tipos de patrones:
Creacional
Los patrones de diseño de programa de creación se ocupan de los mecanismos de construcción de objetos. Tratan de instanciar objetos de una forma idónea para el caso especial. Aquí hay diversos patrones de diseño creacional:
- Factory method
- Abstract factory
- Builder
- Singleton
- Prototype
Estructural
Los patrones de diseño estructural poseen como fin simplificar el diseño al hallar una forma simple de hacer interacciones entre clases y objetos. Los siguientes son algunos patrones de arquitectura estructural:
- Adapter
- Bridge
- Facade
- Decorator
- Composite
- Flyweight
- Proxy
Comportamiento
Los patrones de diseño de comportamiento identifican patrones de comunicación usuales entre entidades e implementan dichos patrones. Los patrones de diseño de comportamiento integran:
- Chain of Responsibility
- Template Method
- Command
- Iterator
- Mediator
- Memento
- Observer
- Strategy
- State
- Visitor
Los más utilizados en Swift
La mayor parte de los tipos de patrones de diseño raramente se aplican y es posible olvidar cómo funcionan inclusive antes de necesitarlos. Por esa razón, hemos seleccionado los 4 patrones de diseño que se usan con más frecuencia en Swift para desarrollar aplicaciones para iOS y otros sistemas operativos.
En los siguientes ejemplos proporcionaremos solo la información importante sobre cada patrón de diseño de programa, es decir, te diremos cómo funcionan a partir de la perspectiva técnica y en qué momento deberían aplicarse.
Builder
El patrón constructor (Builder) es un patrón de diseño que permite producir objetos complicados desde objetos sencillos paso a paso. Este patrón de diseño ayuda a usar el mismo código para producir diferentes perspectivas de objetos.
Imagina que tienes un objeto complejo que necesita incrementarse desde diversos campos y objetos anidados. Comúnmente, el código de inicialización para tales objetos está oculto en un gigantesco constructor con docenas de límites. O peor todavía, puede estar diseminado por todo el código del proyecto.
El patrón de diseño constructor necesita dividir la obra de un objeto de su propia clase. Sin embargo, la obra de este objeto se asigna a objetos especiales denominados constructores y se divide en diversos pasos. Para producir un objeto, llama sucesivamente a los procedimientos del constructor.
Y no se necesita continuar todos los pasos, solo los necesarios para generar un objeto con una configuración especial. Para ejecutar el patrón de diseño constructor debes:
- Cada vez que se quiera evitar la utilización de un constructor telescópico (cuando un constructor tiene demasiados fronteras, se vuelve difícil de leer y administrar);
- Cada vez que tu código necesite generar diferentes vistas de un objeto específico;
- Cuando se requiere componer objetos complicados.
Por ejemplo: Supón que te encuentras desarrollando una aplicación de iOS para una tienda o restaurante y requieras ejecutar la función de peticiones. Puedes optar por incluir dos construcciones: Dish(Plato) y Order(Pedido). Con ayuda de un objeto OrderBuilder puedes elaborar la composición que se pide.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
// Design Patterns: Builder import Foundation // Models enum DishCategory: Int { case firstCourses, mainCourses, garnishes, drinks } struct Dish { var name: String var price: Float } struct OrderItem { var dish: Dish var count: Int } struct Order { var firstCourses: [OrderItem] = [] var mainCourses: [OrderItem] = [] var garnishes: [OrderItem] = [] var drinks: [OrderItem] = [] var price: Float { let items = firstCourses + mainCourses + garnishes + drinks return items.reduce(Float(0), { $0 + $1.dish.price * Float($1.count) }) } } // Builder class OrderBuilder { private var order: Order? func reset() { order = Order() } func setFirstCourse(_ dish: Dish) { set(dish, at: order?.firstCourses, withCategory: .firstCourses) } func setMainCourse(_ dish: Dish) { set(dish, at: order?.mainCourses, withCategory: .mainCourses) } func setGarnish(_ dish: Dish) { set(dish, at: order?.garnishes, withCategory: .garnishes) } func setDrink(_ dish: Dish) { set(dish, at: order?.drinks, withCategory: .drinks) } func getResult() -> Order? { return order ?? nil } private func set(_ dish: Dish, at orderCategory: [OrderItem]?, withCategory dishCategory: DishCategory) { guard let orderCategory = orderCategory else { return } var item: OrderItem! = orderCategory.filter( { $0.dish.name == dish.name } ).first guard item == nil else { item.count += 1 return } item = OrderItem(dish: dish, count: 1) switch dishCategory { case .firstCourses: order?.firstCourses.append(item) case .mainCourses: order?.mainCourses.append(item) case .garnishes: order?.garnishes.append(item) case .drinks: order?.drinks.append(item) } } } // Usage let steak = Dish(name: "Steak", price: 2.30) let chips = Dish(name: "Chips", price: 1.20) let coffee = Dish(name: "Coffee", price: 0.80) let builder = OrderBuilder() builder.reset() builder.setMainCourse(steak) builder.setGarnish(chips) builder.setDrink(coffee) let order = builder.getResult() order?.price // Result: // 4.30 |
Adaptador (Adapter)
El adaptador es un patrón de diseño estructural que permite que los objetos con interfaces incompatibles trabajen unidos. Es decir, este tipo de diseño transforma la interfaz de un objeto para adaptarlo a un objeto distinto.
Un adaptador envuelve un objeto, ocultándose por completo de otro objeto. Por ejemplo, puede envolver un objeto que maneja metros con un adaptador que convierte datos en pulgadas. Cuando se debería usar el patrón de diseño del adaptador
- Cuando se necesite usar una clase de terceros, sin embargo, su interfaz no coincide con lo demás del código de su aplicación;
- Cuando se requiera utilizar numerosas subclases existentes pero se carece de una función especial y, además, no se puede prolongar la superclase.
A modo de ejemplo: Supongamos que se quiere llevar a cabo una función de administración de eventos y calendario en una aplicación iOS. Para hacer esto, deberías integrar el framework de EventKit y adaptar el modelo de eventos del framework al modelo en su aplicación.
Un adaptador puede encapsular el modelo del framework y volverlo compatible con el modelo de su aplicación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// Design Patterns: Adapter import EventKit // Models protocol Event: class { var title: String { get } var startDate: String { get } var endDate: String { get } } extension Event { var description: String { return "Name: \(title)\nEvent start: \(startDate)\nEvent end: \(endDate)" } } class LocalEvent: Event { var title: String var startDate: String var endDate: String init(title: String, startDate: String, endDate: String) { self.title = title self.startDate = startDate self.endDate = endDate } } // Adapter class EKEventAdapter: Event { private var event: EKEvent private lazy var dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "MM-dd-yyyy HH:mm" return dateFormatter }() var title: String { return event.title } var startDate: String { return dateFormatter.string(from: event.startDate) } var endDate: String { return dateFormatter.string(from: event.endDate) } init(event: EKEvent) { self.event = event } } // Usage let dateFormatter = DateFormatter() dateFormatter.dateFormat = "MM/dd/yyyy HH:mm" let eventStore = EKEventStore() let event = EKEvent(eventStore: eventStore) event.title = "Design Pattern Meetup" event.startDate = dateFormatter.date(from: "06/29/2018 18:00") event.endDate = dateFormatter.date(from: "06/29/2018 19:30") let adapter = EKEventAdapter(event: event) adapter.description // Result: // Name: Design Pattern Meetup // Event start: 06-29-2018 18:00 // Event end: 06-29-2018 19:30 |
Decorador (Decodator)
El patrón Decorator es un patrón de diseño estructural que te posibilita adjuntar dinámicamente novedosas funciones a un objeto envolviéndolos en paquetes útiles.
Es de esperar que este patrón de diseño además se llame patrón de diseño Wrapper. Esta denominación explica con mayor exactitud la iniciativa central detrás de este patrón: sitúa un objeto de destino en otro objeto contenedor que desencadena la conducta fundamental del objeto de destino y añade su propio comportamiento al resultado.
Los dos objetos comparten la misma interfaz, por lo cual al cliente no le importa con cuál de los objetos interactúan: limpio o enroscado. Puede usar diversos contenedores al mismo tiempo y obtener la conducta combinada de todos los contenedores. Cuando deberías utilizar el patrón de diseño Decorador
- Cada vez que quieras adicionar responsabilidades a los objetos de manera dinámica y ocultar aquellos objetos del código que los usa;
- Cuando sea imposible alargar las responsabilidades de un objeto por medio de la herencia.
Por ejemplo, imagina que necesitas desarrollar la administración de datos en una aplicación iOS. Puedes generar 2 decoradores: EncryptionDecorator para cifrar y descifrar datos y EncodingDecorator para codificar y decodificar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
// Design Patterns: Decorator import Foundation // Helpers (may be not include in blog post) func encryptString(_ string: String, with encryptionKey: String) -> String { let stringBytes = [UInt8](string.utf8) let keyBytes = [UInt8](encryptionKey.utf8) var encryptedBytes: [UInt8] = [] for stringByte in stringBytes.enumerated() { encryptedBytes.append(stringByte.element ^ keyBytes[stringByte.offset % encryptionKey.count]) } return String(bytes: encryptedBytes, encoding: .utf8)! } func decryptString(_ string: String, with encryptionKey: String) -> String { let stringBytes = [UInt8](string.utf8) let keyBytes = [UInt8](encryptionKey.utf8) var decryptedBytes: [UInt8] = [] for stringByte in stringBytes.enumerated() { decryptedBytes.append(stringByte.element ^ keyBytes[stringByte.offset % encryptionKey.count]) } return String(bytes: decryptedBytes, encoding: .utf8)! } // Services protocol DataSource: class { func writeData(_ data: Any) func readData() -> Any } class UserDefaultsDataSource: DataSource { private let userDefaultsKey: String init(userDefaultsKey: String) { self.userDefaultsKey = userDefaultsKey } func writeData(_ data: Any) { UserDefaults.standard.set(data, forKey: userDefaultsKey) } func readData() -> Any { return UserDefaults.standard.value(forKey: userDefaultsKey)! } } // Decorators class DataSourceDecorator: DataSource { let wrappee: DataSource init(wrappee: DataSource) { self.wrappee = wrappee } func writeData(_ data: Any) { wrappee.writeData(data) } func readData() -> Any { return wrappee.readData() } } class EncodingDecorator: DataSourceDecorator { private let encoding: String.Encoding init(wrappee: DataSource, encoding: String.Encoding) { self.encoding = encoding super.init(wrappee: wrappee) } override func writeData(_ data: Any) { let stringData = (data as! String).data(using: encoding)! wrappee.writeData(stringData) } override func readData() -> Any { let data = wrappee.readData() as! Data return String(data: data, encoding: encoding)! } } class EncryptionDecorator: DataSourceDecorator { private let encryptionKey: String init(wrappee: DataSource, encryptionKey: String) { self.encryptionKey = encryptionKey super.init(wrappee: wrappee) } override func writeData(_ data: Any) { let encryptedString = encryptString(data as! String, with: encryptionKey) wrappee.writeData(encryptedString) } override func readData() -> Any { let encryptedString = wrappee.readData() as! String return decryptString(encryptedString, with: encryptionKey) } } // Usage var source: DataSource = UserDefaultsDataSource(userDefaultsKey: "decorator") source = EncodingDecorator(wrappee: source, encoding: .utf8) source = EncryptionDecorator(wrappee: source, encryptionKey: "secret") source.writeData("Design Patterns") source.readData() as! String // Result: // Design Patterns |
Fachada (Facade)
El patrón Facade es un patrón de diseño estructural capaz de proporcionar una interfaz sencilla simple a una biblioteca, sistema o marco complejo de clases.
Si conoces en algo este mundo, debes saber que cuando se tiene un código que tiene que lidiar con una multiplicidad de objetos es necesario tratar de inicializar todos estos objetos o, en su defecto, hacer un seguimiento del orden de cada una de las dependencias.
En este tipo de patrón se facilita una interfaz que es bastante simple de trabajar con subsistemas que sean complejos, es decir, aquellos que contengan infinidad de clases. A través de esta interfaz es posible ofrecer al cliente solo lo que se necesita y se ocultan todos los objetos o funciones innecesarias.
Ahora bien, ¿Cuándo necesitas usar el diseño de patrón de Fachada?:
- Es muy útil cuando se requiere proporcionar una interfaz unificada a un subsistema complejo que sea simple.
- Cada vez que se tenga un subsistema que requiera descomponerse en capas separadas.
Vamos a suponer que necesitemos admitir una grabación o reproducción de audio en una aplicación móvil. Si este fuera tu caso, vas a requerir implementar esta funcionalidad a través del patrón Facade. Utilizas el sistema de archivos (FileService) para ocultar la implementación de los servicios.
- Para sesiones de audio: AudioSessionService
- Grabación de audio: RecorderService
- Reproducción de audio: PlayerService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
// Design Patterns: Facade import AVFoundation // Services (may be not include in blog post) struct FileService { private var documentDirectory: URL { return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! } var contentsOfDocumentDirectory: [URL] { return try! FileManager.default.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil) } func path(withPathComponent component: String) -> URL { return documentDirectory.appendingPathComponent(component) } func removeItem(at index: Int) { let url = contentsOfDocumentDirectory[index] try! FileManager.default.removeItem(at: url) } } protocol AudioSessionServiceDelegate: class { func audioSessionService(_ audioSessionService: AudioSessionService, recordPermissionDidAllow allowed: Bool) } class AudioSessionService { weak var delegate: AudioSessionServiceDelegate? func setupSession() { try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.defaultToSpeaker]) try! AVAudioSession.sharedInstance().setActive(true) AVAudioSession.sharedInstance().requestRecordPermission { [weak self] allowed in DispatchQueue.main.async { guard let strongSelf = self, let delegate = strongSelf.delegate else { return } delegate.audioSessionService(strongSelf, recordPermissionDidAllow: allowed) } } } func deactivateSession() { try! AVAudioSession.sharedInstance().setActive(false) } } struct RecorderService { private var isRecording = false private var recorder: AVAudioRecorder! private var url: URL init(url: URL) { self.url = url } mutating func startRecord() { guard !isRecording else { return } isRecording = !isRecording recorder = try! AVAudioRecorder(url: url, settings: [AVFormatIDKey: kAudioFormatMPEG4AAC]) recorder.record() } mutating func stopRecord() { guard isRecording else { return } isRecording = !isRecording recorder.stop() } } protocol PlayerServiceDelegate: class { func playerService(_ playerService: PlayerService, playingDidFinish success: Bool) } class PlayerService: NSObject, AVAudioPlayerDelegate { private var player: AVAudioPlayer! private var url: URL weak var delegate: PlayerServiceDelegate? init(url: URL) { self.url = url } func startPlay() { player = try! AVAudioPlayer(contentsOf: url) player.delegate = self player.play() } func stopPlay() { player.stop() } func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { delegate?.playerService(self, playingDidFinish: flag) } } // Facade protocol AudioFacadeDelegate: class { func audioFacadePlayingDidFinish(_ audioFacade: AudioFacade) } class AudioFacade: PlayerServiceDelegate { private let audioSessionService = AudioSessionService() private let fileService = FileService() private let fileFormat = ".m4a" private var playerService: PlayerService! private var recorderService: RecorderService! weak var delegate: AudioFacadeDelegate? private lazy var dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd_HH:mm:ss" return dateFormatter }() init() { audioSessionService.setupSession() } deinit { audioSessionService.deactivateSession() } func startRecord() { let fileName = dateFormatter.string(from: Date()).appending(fileFormat) let url = fileService.path(withPathComponent: fileName) recorderService = RecorderService(url: url) recorderService.startRecord() } func stopRecord() { recorderService.stopRecord() } func numberOfRecords() -> Int { return fileService.contentsOfDocumentDirectory.count } func nameOfRecord(at index: Int) -> String { let url = fileService.contentsOfDocumentDirectory[index] return url.lastPathComponent } func removeRecord(at index: Int) { fileService.removeItem(at: index) } func playRecord(at index: Int) { let url = fileService.contentsOfDocumentDirectory[index] playerService = PlayerService(url: url) playerService.delegate = self playerService.startPlay() } func stopPlayRecord() { playerService.stopPlay() } func playerService(_ playerService: PlayerService, playingDidFinish success: Bool) { if success { delegate?.audioFacadePlayingDidFinish(self) } } } // Usage let audioFacade = AudioFacade() audioFacade.numberOfRecords() // Result: // 0 |
Template Method (Plantilla)
El patrón de método de plantilla es un patrón de diseño de comportamiento que define un esqueleto para un algoritmo y delega la responsabilidad de algunos pasos a las subclases. Este patrón permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura general.
Este patrón de diseño divide un algoritmo en una secuencia de pasos, describe estos pasos en métodos separados y los llama consecutivamente con la ayuda de un método de plantilla única.
Debería utilizar el patrón de diseño del Método de plantilla:
- Cuando las subclases necesitan extender un algoritmo básico sin modificar su estructura;
- Cuando tiene varias clases responsables de acciones bastante similares (lo que significa que cada vez que modifica una clase, necesita cambiar las otras clases).
Suponga que está trabajando en una aplicación de iOS que debe poder tomar y guardar fotografías. Por lo tanto, su aplicación necesita obtener permisos para usar la cámara y la galería de imágenes del iPhone (o iPad).
Para hacer esto, puede usar la clase base PermissionService que tiene un algoritmo específico. Para obtener permiso para usar la cámara y la galería, puede crear dos subclases, CameraPermissionService y PhotoPermissionService, que redefinen ciertos pasos del algoritmo manteniendo los otros pasos iguales.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
// Design Patterns: Template Method import AVFoundation import Photos // Services typealias AuthorizationCompletion = (status: Bool, message: String) class PermissionService: NSObject { private var message: String = "" func authorize(_ completion: @escaping (AuthorizationCompletion) -> Void) { let status = checkStatus() guard !status else { complete(with: status, completion) return } requestAuthorization { [weak self] status in self?.complete(with: status, completion) } } func checkStatus() -> Bool { return false } func requestAuthorization(_ completion: @escaping (Bool) -> Void) { completion(false) } func formMessage(with status: Bool) { let messagePrefix = status ? "You have access to " : "You haven't access to " let nameOfCurrentPermissionService = String(describing: type(of: self)) let nameOfBasePermissionService = String(describing: type(of: PermissionService.self)) let messageSuffix = nameOfCurrentPermissionService.components(separatedBy: nameOfBasePermissionService).first! message = messagePrefix + messageSuffix } private func complete(with status: Bool, _ completion: @escaping (AuthorizationCompletion) -> Void) { formMessage(with: status) let result = (status: status, message: message) completion(result) } } class CameraPermissionService: PermissionService { override func checkStatus() -> Bool { let status = AVCaptureDevice.authorizationStatus(for: .video).rawValue return status == AVAuthorizationStatus.authorized.rawValue } override func requestAuthorization(_ completion: @escaping (Bool) -> Void) { AVCaptureDevice.requestAccess(for: .video) { status in completion(status) } } } class PhotoPermissionService: PermissionService { override func checkStatus() -> Bool { let status = PHPhotoLibrary.authorizationStatus().rawValue return status == PHAuthorizationStatus.authorized.rawValue } override func requestAuthorization(_ completion: @escaping (Bool) -> Void) { PHPhotoLibrary.requestAuthorization { status in completion(status.rawValue == PHAuthorizationStatus.authorized.rawValue) } } } // Usage let permissionServices = [CameraPermissionService(), PhotoPermissionService()] for permissionService in permissionServices { permissionService.authorize { (_, message) in print(message) } } // Result: // You have access to Camera // You have access to Photo |
Conclusión
Hemos descrito cinco de los patrones de diseño estructural que se aplican con más frecuencia en Swift. A través de sencillos ejemplos explicamos cómo llevar a cabo estos patrones de arquitectura de programa en caso de que los requiera.
Seleccionar un patrón de diseño en Swift que sea importante para producir un plan en especial te posibilita generar aplicaciones funcionales y seguras que son sencillas de conservar y actualizar.
Sin lugar a duda, deberías tener patrones de diseño en tu grupo de capacidades, debido a que no sólo simplifican el desarrollo de programa, sino que además optimizan todo el proceso y respaldan una alta calidad del código.
¿Qué te pareció los patrones de diseño para Swift? Déjame tu comentario y no te olvides de compartirla 😄
Comentarios recientes