Buscar..


Introducción

Los patrones de diseño son soluciones generales a problemas que ocurren con frecuencia en el desarrollo de software. Las siguientes son plantillas de mejores prácticas estandarizadas para estructurar y diseñar códigos, así como ejemplos de contextos comunes en los que estos patrones de diseño serían apropiados.

Los patrones de diseño creacional abstraen la creación de instancias de objetos para hacer que un sistema sea más independiente del proceso de creación, composición y representación.

Semifallo

Los Singletons son un patrón de diseño de uso frecuente que consiste en una única instancia de una clase que se comparte a través de un programa.

En el siguiente ejemplo, creamos una propiedad static que contiene una instancia de la clase Foo . Recuerde que una propiedad static se comparte entre todos los objetos de una clase y no puede ser sobrescrita por subclases.

public class Foo
{
    static let shared = Foo()
    
    // Used for preventing the class from being instantiated directly
    private init() {}
    
    func doSomething()
    {
        print("Do something")
    }
}

Uso:

Foo.shared.doSomething()

Asegúrese de recordar el inicializador private :

Esto asegura que sus singletons sean verdaderamente únicos e impide que los objetos externos creen sus propias instancias de su clase a través del control de acceso. Dado que todos los objetos vienen con un inicializador público predeterminado en Swift, debe anular su inicialización y hacerla privada. KrakenDev

Método de fábrica

En la programación basada en clases, el patrón de método de fábrica es un patrón de creación que utiliza métodos de fábrica para tratar el problema de crear objetos sin tener que especificar la clase exacta del objeto que se creará. Referencia de Wikipedia

protocol SenderProtocol
{
    func send(package: AnyObject)
}

class Fedex: SenderProtocol
{
    func send(package: AnyObject)
    {
        print("Fedex deliver")
    }
}

class RegularPriorityMail: SenderProtocol
{
    func send(package: AnyObject)
    {
        print("Regular Priority Mail deliver")
    }
}

// This is our Factory
class DeliverFactory
{
    // It will be responsable for returning the proper instance that will handle the task
    static func makeSender(isLate isLate: Bool) -> SenderProtocol
    {
        return isLate ? Fedex() : RegularPriorityMail()
    }
}

// Usage:
let package = ["Item 1", "Item 2"]

// Fedex class will handle the delivery
DeliverFactory.makeSender(isLate:true).send(package)

// Regular Priority Mail class will handle the delivery
DeliverFactory.makeSender(isLate:false).send(package)

Al hacerlo, no dependemos de la implementación real de la clase, lo que hace que el sender() completamente transparente para quien lo consume.

En este caso, todo lo que necesitamos saber es que un remitente manejará la entrega y expondrá un método llamado send() . Hay varias otras ventajas: reducir el acoplamiento de clases, más fácil de probar, más fácil de agregar nuevos comportamientos sin tener que cambiar quién lo está consumiendo.

Dentro del diseño orientado a objetos, las interfaces proporcionan capas de abstracción que facilitan la explicación conceptual del código y crean una barrera que impide las dependencias. Referencia de Wikipedia

Observador

El patrón de observador es cuando un objeto, llamado sujeto, mantiene una lista de sus dependientes, llamados observadores, y les notifica automáticamente cualquier cambio de estado, generalmente llamando a uno de sus métodos. Se utiliza principalmente para implementar sistemas de manejo de eventos distribuidos. El patrón Observer es también una parte clave en el patrón arquitectónico familiar modelo-vista-controlador (MVC). Referencia de Wikipedia

Básicamente, el patrón de observador se usa cuando tienes un objeto que puede notificar a los observadores sobre ciertos comportamientos o cambios de estado.

Primero, creamos una referencia global (fuera de una clase) para el Centro de notificaciones:

let notifCentre = NotificationCenter.default

Genial ahora podemos llamar a esto desde cualquier lugar. Entonces querríamos registrar una clase como observador ...

notifCentre.addObserver(self, selector: #selector(self.myFunc), name: "myNotification", object: nil)

Esto agrega la clase como un observador para "readForMyFunc". También indica que se debe llamar a la función myFunc cuando se recibe esa notificación. Esta función debe estar escrita en la misma clase:

func myFunc(){
    print("The notification has been received")
}

Una de las ventajas de este patrón es que puede agregar muchas clases como observadores y, por lo tanto, realizar muchas acciones después de una notificación.

La notificación ahora puede simplemente enviarse (o publicarse si lo prefiere) desde casi cualquier lugar del código con la línea:

notifCentre.post(name: "myNotification", object: nil)

También puede pasar información con la notificación como un Diccionario.

let myInfo = "pass this on"
notifCentre.post(name: "myNotification", object: ["moreInfo":myInfo])

Pero entonces necesitas agregar una notificación a tu función:

func myFunc(_ notification: Notification){
    let userInfo = (notification as NSNotification).userInfo as! [String: AnyObject]
    let passedInfo = userInfo["moreInfo"]
    print("The notification \(moreInfo) has been received")
    //prints - The notification pass this on has been received
}

Cadena de responsabilidad

En el diseño orientado a objetos, el patrón de cadena de responsabilidad es un patrón de diseño que consiste en una fuente de objetos de command y una serie de objetos de processing . Cada objeto de processing contiene lógica que define los tipos de objetos de comando que puede manejar; el resto se pasa al siguiente objeto de processing en la cadena. También existe un mecanismo para agregar nuevos objetos de processing al final de esta cadena. Wikipedia

Configuración de las clases que conforman la cadena de responsabilidad.

Primero creamos una interfaz para todos los objetos de processing .

protocol PurchasePower {  
var allowable : Float { get }
  var role : String { get }
  var successor : PurchasePower? { get set }
}

extension PurchasePower {
  func process(request : PurchaseRequest){
    if request.amount < self.allowable {
      print(self.role + " will approve $ \(request.amount) for \(request.purpose)")
    } else if successor != nil {
      successor?.process(request: request)
    }
  }
}

Luego creamos el objeto de command .

struct PurchaseRequest {
  var amount : Float
  var purpose : String
}

Finalmente, creando objetos que conforman la cadena de responsabilidad.

class ManagerPower : PurchasePower {
  var allowable: Float = 20
  var role : String = "Manager"
  var successor: PurchasePower?
}

class DirectorPower : PurchasePower {
  var allowable: Float = 100
  var role = "Director"
  var successor: PurchasePower?
}

class PresidentPower : PurchasePower {
  var allowable: Float = 5000
  var role = "President"
  var successor: PurchasePower?
}

Iniciar y encadenar juntos:

let manager = ManagerPower()
let director = DirectorPower()
let president = PresidentPower()

manager.successor = director
director.successor = president

El mecanismo para encadenar objetos aquí es el acceso a la propiedad.

Creando solicitud para ejecutarlo:

manager.process(request: PurchaseRequest(amount: 2, purpose: "buying a pen"))  // Manager will approve $ 2.0 for buying a pen
manager.process(request: PurchaseRequest(amount: 90, purpose: "buying a printer")) // Director will approve $ 90.0 for buying a printer

manager.process(request: PurchaseRequest(amount: 2000, purpose: "invest in stock")) // President will approve $ 2000.0 for invest in stock

Iterador

En la programación de computadoras, un iterador es un objeto que le permite a un programador atravesar un contenedor, particularmente listas. Wikipedia

struct Turtle {
  let name: String
}

struct Turtles {
  let turtles: [Turtle]
}

struct TurtlesIterator: IteratorProtocol {
  private var current = 0
  private let turtles: [Turtle]

  init(turtles: [Turtle]) {
    self.turtles = turtles
  }

  mutating func next() -> Turtle? {
    defer { current += 1 }
    return turtles.count > current ? turtles[current] : nil
  }
}

extension Turtles: Sequence {
  func makeIterator() -> TurtlesIterator {
    return TurtlesIterator(turtles: turtles)
  }
}

Y el ejemplo de uso sería

let ninjaTurtles = Turtles(turtles: [Turtle(name: "Leo"),
                                     Turtle(name: "Mickey"),
                                     Turtle(name: "Raph"),
                                     Turtle(name: "Doney")])
print("Splinter and")
for turtle in ninjaTurtles {
  print("The great: \(turtle)")
}

Patrón de constructor

El patrón de construcción es un patrón de diseño de software de creación de objetos . A diferencia del patrón de fábrica abstracto y el patrón de método de fábrica cuya intención es habilitar el polimorfismo, la intención del patrón de construcción es encontrar una solución para el constructor telescópico anti-patrón. El antipatrón del constructor telescópico se produce cuando el aumento de la combinación de parámetros del constructor de objetos conduce a una lista exponencial de constructores. En lugar de utilizar numerosos constructores, el patrón de construcción utiliza otro objeto, un generador, que recibe cada parámetro de inicialización paso a paso y luego devuelve el objeto construido resultante de una vez.

-Wikipedia

El objetivo principal del patrón de creación es configurar una configuración predeterminada para un objeto desde su creación. Es un intermediario entre el objeto que se construirá y todos los demás objetos relacionados con su creación.

Ejemplo:

Para que quede más claro, echemos un vistazo a un ejemplo de Car Builder .

Tenga en cuenta que tenemos una clase de automóvil que contiene muchas opciones para crear un objeto, como:

  • Color.
  • Numero de asientos.
  • Número de ruedas.
  • Tipo.
  • Tipo de engranaje.
  • Motor.
  • Disponibilidad de bolsas de aire.
import UIKit

enum CarType {
    case
    
    sportage,
    saloon
}

enum GearType {
    case
    
    manual,
    automatic
}

struct Motor {
    var id: String
    var name: String
    var model: String
    var numberOfCylinders: UInt8
}

class Car: CustomStringConvertible {
    var color: UIColor
    var numberOfSeats: UInt8
    var numberOfWheels: UInt8
    var type: CarType
    var gearType: GearType
    var motor: Motor
    var shouldHasAirbags: Bool
    
    var description: String {
        return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
    }
    
    init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
        
        self.color = color
        self.numberOfSeats = numberOfSeats
        self.numberOfWheels = numberOfWheels
        self.type = type
        self.gearType = gearType
        self.motor = motor
        self.shouldHasAirbags = shouldHasAirbags
        
    }
}

Creando un objeto de coche:

let aCar = Car(color: UIColor.black,
               numberOfSeats: 4,
               numberOfWheels: 4,
               type: .saloon,
               gearType: .automatic,
               motor: Motor(id: "101", name: "Super Motor",
                            model: "c4", numberOfCylinders: 6),
               shouldHasAirbags: true)

print(aCar)

/* Printing
 color: UIExtendedGrayColorSpace 0 1
 Number of seats: 4
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "101", name: "Super Motor", model: "c4", numberOfCylinders: 6)
 Airbag Availability: true
*/

El problema que surge al crear un objeto de automóvil es que el automóvil requiere que se creen muchos datos de configuración.

Para aplicar el patrón del generador, los parámetros del inicializador deben tener valores predeterminados que pueden modificarse si es necesario .

Clase CarBuilder:

class CarBuilder {
    var color: UIColor = UIColor.black
    var numberOfSeats: UInt8 = 5
    var numberOfWheels: UInt8 = 4
    var type: CarType = .saloon
    var gearType: GearType = .automatic
    var motor: Motor = Motor(id: "111", name: "Default Motor",
                             model: "T9", numberOfCylinders: 4)
    var shouldHasAirbags: Bool = false
    
    func buildCar() -> Car {
        return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
    }
}

La clase CarBuilder define propiedades que podrían cambiarse para editar los valores del objeto de automóvil creado.

Construyamos autos nuevos usando el CarBuilder :

var builder = CarBuilder()
// currently, the builder creates cars with default configuration.

let defaultCar = builder.buildCar()
//print(defaultCar.description)
/* prints
 color: UIExtendedGrayColorSpace 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: false
*/

builder.shouldHasAirbags = true
// now, the builder creates cars with default configuration,
// but with a small edit on making the airbags available

let safeCar = builder.buildCar()
print(safeCar.description)
/* prints
 color: UIExtendedGrayColorSpace 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: true
 */

builder.color = UIColor.purple
// now, the builder creates cars with default configuration
// with some extra features: the airbags are available and the color is purple

let femaleCar = builder.buildCar()
print(femaleCar)
/* prints
 color: UIExtendedSRGBColorSpace 0.5 0 0.5 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: true
*/

La ventaja de aplicar el patrón de creación es la facilidad de crear objetos que deben contener gran parte de las configuraciones mediante el establecimiento de valores predeterminados, así como la facilidad de cambiar estos valores predeterminados.

Llevarlo mas alla:

Como una buena práctica, todas las propiedades que necesitan valores predeterminados deben estar en un protocolo separado , que debe ser implementado por la clase en sí y su generador.

Regresando a nuestro ejemplo, vamos a crear un nuevo protocolo llamado CarBluePrint :

import UIKit

enum CarType {
    case
    
    sportage,
    saloon
}

enum GearType {
    case
    
    manual,
    automatic
}

struct Motor {
    var id: String
    var name: String
    var model: String
    var numberOfCylinders: UInt8
}

protocol CarBluePrint {
    var color: UIColor { get set }
    var numberOfSeats: UInt8 { get set }
    var numberOfWheels: UInt8 { get set }
    var type: CarType { get set }
    var gearType: GearType { get set }
    var motor: Motor { get set }
    var shouldHasAirbags: Bool { get set }
}

class Car: CustomStringConvertible, CarBluePrint {
    var color: UIColor
    var numberOfSeats: UInt8
    var numberOfWheels: UInt8
    var type: CarType
    var gearType: GearType
    var motor: Motor
    var shouldHasAirbags: Bool
    
    var description: String {
        return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
    }
    
    init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
        
        self.color = color
        self.numberOfSeats = numberOfSeats
        self.numberOfWheels = numberOfWheels
        self.type = type
        self.gearType = gearType
        self.motor = motor
        self.shouldHasAirbags = shouldHasAirbags
        
    }
}

class CarBuilder: CarBluePrint {
    var color: UIColor = UIColor.black
    var numberOfSeats: UInt8 = 5
    var numberOfWheels: UInt8 = 4
    var type: CarType = .saloon
    var gearType: GearType = .automatic
    var motor: Motor = Motor(id: "111", name: "Default Motor",
                             model: "T9", numberOfCylinders: 4)
    var shouldHasAirbags: Bool = false
    
    func buildCar() -> Car {
        return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
    }
}

La ventaja de declarar las propiedades que necesitan un valor predeterminado en un protocolo es la obligación de implementar cualquier nueva propiedad agregada; Cuando una clase se ajusta a un protocolo, tiene que declarar todas sus propiedades / métodos.

Tenga en cuenta que hay una nueva característica requerida que se debe agregar al plan de creación de un automóvil llamado "nombre de la batería":

protocol CarBluePrint {
    var color: UIColor { get set }
    var numberOfSeats: UInt8 { get set }
    var numberOfWheels: UInt8 { get set }
    var type: CarType { get set }
    var gearType: GearType { get set }
    var motor: Motor { get set }
    var shouldHasAirbags: Bool { get set }
    
    // adding the new property
    var batteryName: String { get set }
}

Después de agregar la nueva propiedad, tenga en cuenta que surgirán dos errores de tiempo de compilación y notificará que para CarBluePrint protocolo CarBluePrint necesario declarar la propiedad 'batteryName'. Eso garantiza que CarBuilder declarará y establecerá un valor predeterminado para la propiedad batteryName .

Después de añadir batteryName nueva propiedad a CarBluePrint protocolo, la aplicación de ambos Car y CarBuilder clases debe ser:

class Car: CustomStringConvertible, CarBluePrint {
    var color: UIColor
    var numberOfSeats: UInt8
    var numberOfWheels: UInt8
    var type: CarType
    var gearType: GearType
    var motor: Motor
    var shouldHasAirbags: Bool
    var batteryName: String
    
    var description: String {
        return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\nType: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)\nBattery Name: \(batteryName)"
    }
    
    init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool, batteryName: String) {
        
        self.color = color
        self.numberOfSeats = numberOfSeats
        self.numberOfWheels = numberOfWheels
        self.type = type
        self.gearType = gearType
        self.motor = motor
        self.shouldHasAirbags = shouldHasAirbags
        self.batteryName = batteryName
    }
}

class CarBuilder: CarBluePrint {
    var color: UIColor = UIColor.red
    var numberOfSeats: UInt8 = 5
    var numberOfWheels: UInt8 = 4
    var type: CarType = .saloon
    var gearType: GearType = .automatic
    var motor: Motor = Motor(id: "111", name: "Default Motor",
                             model: "T9", numberOfCylinders: 4)
    var shouldHasAirbags: Bool = false
    var batteryName: String = "Default Battery Name"
    
    func buildCar() -> Car {
        return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags, batteryName: batteryName)
    }
}

Nuevamente, construyamos autos nuevos usando el CarBuilder :

var builder = CarBuilder()

let defaultCar = builder.buildCar()
print(defaultCar)
/* prints
 color: UIExtendedSRGBColorSpace 1 0 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: false
 Battery Name: Default Battery Name
*/

builder.batteryName = "New Battery Name"

let editedBatteryCar = builder.buildCar()
print(editedBatteryCar)
/*
 color: UIExtendedSRGBColorSpace 1 0 0 1
 Number of seats: 5
 Number of Wheels: 4
 Type: automatic
 Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
 Airbag Availability: false
 Battery Name: New Battery Name
 */


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow