Patrón de diseño: BUILDER

Hoy vamos a ver un patrón de creación llamado Builder. Es de los más comunes y útiles para crear instancias debido a su fácil implementación y a los beneficios que nos aportan.

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇

Este es un patrón de diseño creacional. El patrón Builder nos ayuda a construir objetos complejos paso por paso.

Imagina que queremos contruir un coche. Si queremos crear un coche pasando argumentos a un inicializador, puede que al final tengamos un inicializador con muchos parámetros y muchos de ellos no se usarían en la mayoría de casos.
Es decir, podemos crear un coche modificando las características que queremos en él: tamaño de las ruedas, número de puertas, material de los asientos, tamaño del motor, color del chasis, tipo de gasolina, etc.
Nuestros tipos de coches pueden variar, pero si ponemos todas estas opciones en un inicializador el número de parámetros sería infinito.

enum CarSize {
    case small
    case medium
    case big
}

class Car {
    let numberOfDoors: Int
    let color: UIColor
    let size: CarSize
    
    init(numberOfDoors: Int, color: UIColor, size: CarSize) {
        self.numberOfDoors = numberOfDoors
        self.color = color
        self.size = size
    }
}

Podemos crear una clase base y usarla para heredar de ella cuando queramos modificar propiedades de nuestro coche. Pero a medida que añadamos más  parámetros tendremos que incrementar nuestro inicializador. Vamos a ver un ejemplo:

enum EngineSize {
    case small
    case medium
    case big
}

enum GearsType {
    case manual
    case automatic
}

class SportCar: BaseCar {
    let motor: EngineSize
    let gears: GearsType
    
    init(numberOfDoors: Int, color: UIColor, size: CarSize, motor: EngineSize, gears: GearsType) {
        self.motor = motor
        self.gears = gears
        super.init(numberOfDoors: numberOfDoors, color: color, size: size)
    }
}

Acabamos de crear la clase SportCar que hereda de BaseCar. Hemos creado dos propiedades nuevas para los coches deportivos: motor y gears (marchas). Como ves, a parte de crear los parámetros necesarios para instanciar SportCar, también necesitamos los parámetros necesarios para la clase base (numberOfDoors, color y size). ¿Qué crees que pasará si necesitamos heredar de SportCar para crear un tipo de coche con asientos de piel? Exacto, nuestro inicializador crecerá aún más para esa clase.

Para evitar los problemas que acabamos de ver, puedes pensar que una posible solución es crear un inicializador con todos los parámetros posibles que usas en tus subclases, pero al final acabas teniendo muchos parámetros que no vas a usar y que tienen valores por defecto. Para que te hagas una idea, sería algo así:

class CustomCar {
    var numberOfDoors: Int?
    var color: UIColor?
    var size: CarSize?
    var motor: EngineSize?
    var gears: GearsType?
    
    init(numberOfDoors: Int?, color: UIColor?, size: CarSize?, motor: EngineSize?, gears: GearsType?) {
        self.numberOfDoors = numberOfDoors
        self.color = color
        self.size = size
        self.motor = motor
        self.gears = gears
    }
    
    convenience init() {
        self.init(numberOfDoors: nil, color: nil, size: nil, motor: nil, gears: nil)
    }
}

let myCustomCar = CustomCar(numberOfDoors: 4, color: .black, size: .medium, motor: .big, gears: .automatic)

Para evitar estos problemas, podemos usar el patrón Builder para poder crear un objeto paso por paso, en este caso crearemos una clase builder que nos cree un coche. Vamos a ver un ejemplo:

class CarBuilder {
    private var car: CustomCar
    
    init() {
        self.car = CustomCar()
    }
    
    func addDoors(numberOfDoors: Int) -> CarBuilder {
        self.car.numberOfDoors = numberOfDoors
        return self
    }
    
    func addColor(color: UIColor) -> CarBuilder {
        self.car.color = color
        return self
    }
    
    func addSize(size: CarSize) -> CarBuilder {
        self.car.size = size
        return self
    }
    
    func addMotor(size: EngineSize) -> CarBuilder {
        self.car.motor = size
        return self
    }
    
    func build() -> CustomCar {
        return self.car
    }
}

Hemos creado una clase CarBuilder con una propiedad que es de tipo CustomCar y unos métodos para cambiar las propiedades de nuestro coche. El último método es el más importante ya que nos va a devolver la instancia del CustomCar con todas las propiedas que hayamos modificado.
Al inicializar el builder creamos un CustomCar con todas sus propiedades a nil.

Por lo tanto, si queremos crear un coche con 2 puertas, y con tamaño grande, solo tendríamos que hacer lo siguiente:

let myNewCar = CarBuilder().addDoors(numberOfDoors: 2)
                           .addSize(size: .big)
                           .addMotor(size: .big)
                           .build()

Podemos crear nuestro coche "a la carta" creando métodos en nuestro builder que cambien por debajo la instancia de CustomCar.

Hasta aquí el post de hoy, gracias por leernos! 🤓
Si tienes preguntas no dudes en contactar con nosotros a través de Twitter