Skip to content

JosephCalla/Design-Patterns-Swift

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

84 Commits
 
 
 
 
 
 

Repository files navigation

Design-Patterns-Swift

Structural patterns Behavioral patterns Creational patterns
✌️ Adapter Chain of Responsibility 🚧 Factory Method
Decorator Command 👷‍ Builder
Bridge Iterator 🧘 Singleton
Composite Mediator Abstract Factory
🪟 Facade Memento Prototype
👮‍♀️ Proxy 🧐 Observer
Flyweight State
Coordinator Strategy
Template Method
Visitor

SOLID

  • S - Single responsability principle
  • O - Open/Closed principle
  • L - Liskov substitution principle
  • I - Interface segregation principle
  • D - Dependency inversion principle

Single responsability principle

A class should have one responsability.

class Car {
	var licensePlate: String
	init(licensePlate: String) { self.licensePlate = licensePlate }
}
class CarBD {
	func saveCarDB(car: Car) {}
	func deleteCarDB(car: Car) {}
}

Back to SOLID

Open/Closed

Software entities, including classes, modules and functions, should be open for extension but closed for modification.

This means you should be able to expand the capabilities of your types without having to alter them drastically to add what you need.

Examples 1

import UIKit

protocol LoginServiceProtocol {
    func login(completion: @escaping (Bool) -> Void)
}

class LoginService: LoginServiceProtocol {
    func login(completion: @escaping (Bool) -> Void) {
        URLSession.shared.dataTask(with: URL(string: "https://any-url.com/")!) { data, response, error in
            if let _ = error {
                completion(false)
            } else {
                //logic
                completion(true)
            }
        }.resume ()
    }
}

class LoginFacebookService: LoginServiceProtocol {
    func login(completion: @escaping (Bool) -> Void) {
        // SDK Facebook
	completion(true)
    }
}

Examples 2

❌ BAD

class Car {
    var brand: String
    init(brand: String) { self.brand = brand }
}
var cars: [Car] = [
    Car(brand: "Ford"),
    Car(brand: "Chevrolet")
] 
func printCarsPrice(_ cars: [Car]) {
    for car in cars {
        if car.brand == "Ford" { print(2000)  } // 2000
        if car.brand == "Chevrolet" { print(3200)  } // 3000
    }
}
printCarsPrice(cars)

Why is that code is ❌ Bad ?

Because this does not follow the open-closed principle, since if we wanted to add a new car:

var cars: [Car] = [
	Car(brand: "Ford"),
   	Car(brand: "Chevrolet"),
   	Car(brand: "Jeep")   	    
]	 

We would also have to modify the method we created previously:

func printCarsPrice(_ cars: [Car]) {
    for car in cars {
        if car.brand == "Ford" { print(2000)  } // 2000
        if car.brand == "Chevrolet" { print(3200)  } // 3000
        if car.brand == "Jeep" { print(1450)  } // 1450
    }
}

✅ GOOD

protocol Car { func price()-> Int }

class Ford: Car {
    func price()-> Int { return 2000 }
}
class Chevrolet: Car {
    func price()-> Int { return 3000 }
}
class Jeep: Car {
    func price()-> Int { return 1450 }
}
var cars: [Car] = [
    Chevrolet(),
    Ford(),
    Jeep()
] 

func printCarsPrice(_ cars: [Car]) {
    for car in cars {
        print(car.price())
    }
}

printCarsPrice(cars)

Back to SOLID

Liskov Substitution

Establishes that a class that inherits from another can be used as its parent without needing to know the differences between them.

In other words, if you replace one object with another that’s a subclass and this replacement could break the affected part, then you’re not following this principle.

protocol UserDataBaseManagerProtocol {
  func saveUser(user: User)
}

class UserDataBaseManager: UserDataBaseManagerProtocol {
  func saveUser(user: User) {
  	// Save user on DB
  }
}

Back to SOLID

Interface segregation

Clients should not be forced to depend upon interfaces they do not use.

When designing a protocol you’ll use in different places in your code, it’s best to break that protocol into multiple smaller pieces where each piece has a specific role. That way, clients depend only on the part of the protocol they need.

Take for example this struct

protocol Bird {
    func eat() 
    func fly() 
}

class Pigeon: Bird {
    func eat() {}
    func fly() {}
}
class Parrot: Bird  {
    func eat() {}
    func fly() {}
}

⚠️ Well, but now i want to add a new Penguien class. As you know, they're birds but it can swimming also.

protocol Bird {
    func eat() 
    func fly() 
    func swim() 
}

class Pigeon: Bird {
    func eat() {}
    func fly() {}
    func swim() {}
}
class Parrot: Bird  {
    func eat() {}
    func fly() {}
    func swim() {}
}
class Penguin: Bird  {
    func eat() {}
    func fly() {}
    func swim() {}
}

The problem is that dove 🕊️ can't swimming and pingüino 🐧 can't fly. The solution would be se Interface segregation. ✅

protocol Bird {
    func eat()  
}

protocol FlyingBird {
    func fly() 
}
protocol SwimmingBird {
    func swim() 
}

class Pigeon: Bird, FlyingBird  {
    func eat() {}
    func fly() {}
}
class Penguin: Bird, SwimmingBird {
    func eat() {}
    func swim() {}
}

Back to SOLID

Dependency inversion

Depend upon abstractions, not concretions.

Different parts of your code should not depend on concrete classes. They don’t need that knowledge. This encourages the use of protocols instead of using concrete classes to connect parts of your app.

Back to SOLID

Creational Patterns

🚧 Factory Method

Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

image

Example

// Factory Method - Creational
protocol Payment {
    func doPayment()
}

class GooglePayment: Payment {
    func doPayment() {
        print("Haciendo el pago con GOOGLE PAYMENT")
    }
}

class CardPayment: Payment {
    func doPayment() {
        print("Haciendo el pago con Tarjeta de Crédito")
    }
}

enum TypePayment {
    case GOOGLE
    case CARD
}

class PaymentFactory {
    static func buildPayment(typePayment: TypePayment) -> Payment {
        switch typePayment {
        case .GOOGLE:
            return GooglePayment()
            
        case .CARD:
            return CardPayment()
        }
    }
}

// Show Result ->
func testFactoryMethod() {
    var payment: Payment
    payment = PaymentFactory.buildPayment(typePayment: .GOOGLE)
    payment.doPayment()
}

testFactoryMethod()

Example 2

// Se define un protocolo llamado "Vehicle" que incluye un método "transport"
// que recibe un parámetro "passengers"
protocol Vehicle {
    func transport(passengers: Int)
}

// Se define una clase "Car" que implementa el protocolo "Vehicle"
// y provee una implementación para el método "transport"
class Car: Vehicle {
    func transport(passengers: Int) {
        print("Transporting \(passengers) passengers in a car")
    }
}

// Se define una clase "Bus" que implementa el protocolo "Vehicle"
// y provee una implementación para el método "transport"
class Bus: Vehicle {
    func transport(passengers: Int) {
        print("Transporting \(passengers) passengers in a bus")
    }
}

// Se define una clase "Train" que implementa el protocolo "Vehicle"
// y provee una implementación para el método "transport"
class Train: Vehicle {
    func transport(passengers: Int) {
        print("Transporting \(passengers) passengers in a train")
    }
}

// Se define una clase "VehicleFactory" que contiene un método de clase "createVehicle"
// que recibe un parámetro "type" y devuelve una instancia del tipo de vehículo especificado.
// Si el tipo especificado no es válido, devuelve "nil"
class VehicleFactory {
    static func createVehicle(type: String) -> Vehicle? {
        switch type {
            case "car":
                return Car()
            case "bus":
                return Bus()
            case "train":
                return Train()
            default:
                return nil
        }
    }
}

// Se crea una instancia de "Car" utilizando la fábrica y se llama al método "transport"
let car = VehicleFactory.createVehicle(type: "car")
car?.transport(passengers: 4) // Transporting 4 passengers in a car

// Se crea una instancia de "Bus" utilizando la fábrica y se llama al método "transport"
let bus = VehicleFactory.createVehicle(type: "bus")
bus?.transport(passengers: 20) // Transporting 20 passengers in a bus

Advantage of Factory Method Pattern

  • 🟢 You avoid tight coupling between the creator and the concrete products.
  • 🟢 Single Responsibility Principle. You can move the product creation code into one place in the program, making the code easier to support.
  • 🟢 Open/Closed Principle. You can introduce new types of products into the program without breaking existing client code.

Disadvantages of Factory Method Pattern

  • 🔴 The code may become more complicated since you need to introduce a lot of new subclasses to implement the pattern. The best case scenario is when you’re introducing the pattern into an existing hierarchy of creator classes.

🔙 Back To Menu

👷‍♂️ Builder Pattern

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

Screenshot 2022-11-24 at 00 09 42

Example

class Card {
    private var cardType: String = ""
    private var number: String = ""
    private var expired: Int = 0
    
    func showCard() {
        print("Tarjeta \(cardType) - \(number) - \(expired)")
    }
    
    class CardBuilder {
        private var innerCard = Card()
        
        func cartType(cardType: String) -> CardBuilder {
            innerCard.cardType = cardType
            return self
        }
        
        func number(number: String) -> CardBuilder {
            innerCard.number = number
            return self
        }
        
        func expires(expires: Int) -> CardBuilder {
            innerCard.expired = expires
            return self
        }
        
        func build() -> Card {
            return innerCard
        }
    }
}

// Test
func testBuilder() {
    var card: Card = Card.CardBuilder()
        .cartType(cardType: "VISA")
        .build()
    card.showCard()
}

testBuilder()
import Foundation

// The Product class represents the object that we want to build
class Product {
  // Properties of the Product object
  var name: String
  var price: Double
  var quantity: Int

  // Initializes a new Product instance with the specified properties
  init(name: String, price: Double, quantity: Int) {
    self.name = name
    self.price = price
    self.quantity = quantity
  }
}

// The ProductBuilder class is a builder for creating instances of Product
class ProductBuilder {
  // Properties of the Product object
  private var name: String = ""
  private var price: Double = 0.0
  private var quantity: Int = 0

  // Sets the name of the Product object and returns the builder
  func setName(name: String) -> ProductBuilder {
    self.name = name
    return self
  }

  // Sets the price of the Product object and returns the builder
  func setPrice(price: Double) -> ProductBuilder {
    self.price = price
    return self
  }

  // Sets the quantity of the Product object and returns the builder
  func setQuantity(quantity: Int) -> ProductBuilder {
    self.quantity = quantity
    return self
  }

  // Creates and returns a new Product instance with the specified properties
  func build() -> Product {
    return Product(name: self.name, price: self.price, quantity: self.quantity)
  }
}

// Create a new Product instance using the builder
let product = ProductBuilder()
  .setName(name: "Product A")
  .setPrice(price: 99.99)
  .setQuantity(quantity: 10)
  .build()

// Print the properties of the Product instance
print(product.name)  // prints "Product A"
print(product.price) // prints 99.99
print(product.quantity) // prints 10

When to use it

  • When you have an object with many optional parameters that can be set in different combinations.
  • When you want to create an immutable object.
  • When you want to create a complex object in a simple way.
  • When you want to create a customizable object.

Advantage of Builder Pattern

  • 🟢 You can construct objects step-by-step, defer construction steps or run steps recursively.
  • 🟢 You can reuse the same construction code when building various representations of products.
  • 🟢 Single Responsibility Principle. You can isolate complex construction code from the business logic of the product.

Disadvantages of Builder Pattern

  • 🔴 The overall complexity of the code increases since the pattern requires creating multiple new classes.

🔙 Back To Menu

🧘 Singleton Pattern

Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.

image

class CardSingleton {
    static var shared = CardSingleton() // Singleton
    
    private init(){} // Important!
    
    func doSomething() {
        print("Haciendo trabajo de la clase Singleton")
    }
}

func testSingleton() {
    let instancia1 = CardSingleton.shared
    let instancia1 = CardSingleton.shared
    
    // Just for testing to verify that they're have only one instance
    if instancia2 == instancia1 {
        print("La instancia1 es la misma que la 2")
    }
}

Advantage of Singleton Pattern

  • 🟢 You can be sure that a class has only a single instance.
  • 🟢 You gain a global access point to that instance.
  • 🟢 The singleton object is initialized only when it’s requested for the first time.

Disadvantages of Singleton Pattern

  • 🔴 Violates the Single Responsibility Principle. The pattern solves two problems at the time.
  • 🔴 The Singleton pattern can mask bad design, for instance, when the components of the program know too much about each other.
  • 🔴 The pattern requires special treatment in a multithreaded environment so that multiple threads won’t create a singleton object several times.
  • 🔴 It may be difficult to unit test the client code of the Singleton because many test frameworks rely on inheritance when producing mock objects. Since the constructor of the singleton class is private and overriding static methods is impossible in most languages, you will need to think of a creative way to mock the singleton. Or just don’t write the tests. Or don’t use the Singleton pattern.

🔙 Back To Menu

Behavioral patterns

🧐 Observer Pattern

Observer is a behavioral design pattern that allows some objects to notify other objects about changes in their state.

The Observer pattern provides a way to subscribe and unsubscribe to and from these events for any object that implements a subscriber interface.

Screenshot 2022-11-24 at 10 15 25

  • Subject:
  • Observer:
  • ConcreteObserver:
  • NotifyObservers:
struct TrafficLight {
    var status: String
}

/// Observer: Es la interfaz que define las operaciones que se utilizan para notificar al "Subject"
protocol Observer {
    func update(traffictLight: TrafficLight)
}


protocol Subject {
    func addObserver(o: Observer)
    func deleteObserver(o: Observer)
    func notifyUpdate(trafficLight: TrafficLight)
}


class MessagePublisher: Subject {
    var observers = [Observer]()
    func addObserver(o: Observer) {
        observers.append(o)
    }
    
    func deleteObserver(o: Observer) {
        if let index = observers.firstIndex(where: { $0 as AnyObject === o as AnyObject}) {
            observers.remove(at: index)
        }
    }
    
    func notifyUpdate(trafficLight: TrafficLight) {
        observers.forEach { $0.update(traffictLight: trafficLight) }
    }
}

/// ConcreteObserverA
class  Car: Observer {
    func update(traffictLight: TrafficLight) {
        if traffictLight.status as AnyObject === "CAR_RED" as AnyObject {
            print("Semaforo coche Rojo -> Coche NO puede pasar")
        } else {
            print("Semaforo coche Verde -> Coche SI puede pasar")
        }
    }
}


/// ConcreteObserverB
class Pedestrian: Observer {
    func update(traffictLight: TrafficLight) {
        if traffictLight.status as AnyObject === "CAR_RED" as AnyObject {
            print("Semaforo peaton Verde -> PEATON SI puede pasar")
        } else {
            print("Semaforo peaton Rojo -> PEATON NO puede pasar")
        }
    }
}


// TEST
func testObserver() {
    let car = Car()
    let pedestrian = Pedestrian()
    var trafficLight = TrafficLight(status: "CAR_GREEN")
    let messagePublisher = MessagePublisher()
    
    messagePublisher.addObserver(o: car)
    messagePublisher.addObserver(o: pedestrian)
    messagePublisher.notifyUpdate(trafficLight: trafficLight)
    sleep(2)
    print("After sleep")
    TrafficLight(status: "CARD_RED")
    messagePublisher.notifyUpdate(trafficLight: trafficLight)
}

testObserver()

Advantage of Observer Pattern

  • 🟢 Open/Closed Principle. You can introduce new subscriber classes without having to change the publisher’s code (and vice versa if there’s a publisher interface).
  • 🟢 You can establish relations between objects at runtime.

Disadvantages of Observer Patter

  • 🔴 Subscribers are notified in random order.

🔙 Back To Menu

Visitor Pattern

Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.

The Visitor pattern suggests that you place the new behavior into a separate class called visitor, instead of trying to integrate it into existing classes. The original object that had to perform the behavior is now passed to one of the visitor’s methods as an argument, providing the method access to all necessary data contained within the object.

Screenshot 2022-12-02 at 09 58 43

protocol CrediCardVisitor {
    func gassolineOffer(gassolineOffer: GasolinaOffer)
    func flightOffer(flightOffer: FlightsOffer)
}

protocol OffertElement {
    func accept(visitor: CrediCardVisitor)
}

class GasolinaOffer: OffertElement {
    func accept(visitor: CrediCardVisitor) {
        visitor.gassolineOffer(gassolineOffer: self)
    }
}

class FlightsOffer: OffertElement {
    func accept(visitor: CrediCardVisitor) {
        visitor.flightOffer(flightOffer: self)
    }
}

class ClassicCreditCardVisitor: CrediCardVisitor {
    func gassolineOffer(gassolineOffer: GasolinaOffer) {
        print("Descuento 3% en Gasolina con tu tarjeta clasica")
    }
    
    func flightOffer(flightOffer: FlightsOffer) {
        print("Descuento 5% en vuelos con tu tarjeta clasica")
    }
}


class BlackCreditCardVisitor: CrediCardVisitor {
    func gassolineOffer(gassolineOffer: GasolinaOffer) {
        print("Descuento 10% en Gasolina con tu tarjeta Black")
    }
    
    func flightOffer(flightOffer: FlightsOffer) {
        print("Descuento 25% en Vuelos con tu tarjeta Black")
    }
}


// TEST
func testVisitor() {
    let oferta = GasolinaOffer()
    oferta.accept(visitor: BlackCreditCardVisitor())
}

testVisitor()
// Print
// Descuento 10% en Gasolina con tu tarjeta Black

🔙 Back To Menu

Strategy

Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

The Strategy pattern suggests that you take a class that does something specific in a lot of different ways and extract all of these algorithms into separate classes called strategies.

Screenshot 2022-12-02 at 12 41 55

protocol StrategyTextFormatter {
    func format(text: String)
}

class CapitalStategyTextFormatter: StrategyTextFormatter {
    func format(text: String) {
        print("Texto en Mayusculas: \(text.uppercased())")
    }
}

class LowerStategyTextFormatter: StrategyTextFormatter {
    func format(text: String) {
        print("Texto en Minusculas: \(text.lowercased())")
    }
}

class Context {
    var strategyTextFormatter: StrategyTextFormatter
    
    init(strategyTextFormatter: StrategyTextFormatter) {
        self.strategyTextFormatter = strategyTextFormatter
    }
    
    func publishText(text: String) {
        strategyTextFormatter.format(text: text)
    }
}

// TEST
func testStrategy() {
    let contextCapital = Context(strategyTextFormatter: CapitalStategyTextFormatter())
    contextCapital.publishText(text: "este texto sera convertido a Mayusculas a traves de nuestro algoritmo")
    
    let contextLower = Context(strategyTextFormatter: LowerStategyTextFormatter())
    contextLower.publishText(text: "este texto sera convertido a Minusculas a traves de nuestro algoritmo")
}

testStrategy()

// RESULT
///Texto en Mayusculas: ESTE TEXTO SERA CONVERTIDO A MAYUSCULAS A TRAVES DE NUESTRO ALGORITMO
///Texto en Minusculas: este texto sera convertido a minusculas a traves de nuestro algoritmo

🔙 Back To Menu

Structural patterns

Coodinator Pattern

Gestiona la navegación y el flujo de trabajo en la aplicación. Se basa en separar la lógica de navegación y flujo de trabajo de la vista y el controlador de vista para mejorar la separación de responsabilidades y facilitar la prueba y el mantenimiento de la aplicación

class AppCoordinator: Coordinator {
  var window: UIWindow
  var rootViewController: UINavigationController

  init(window: UIWindow) {
    self.window = window
    self.rootViewController = UINavigationController()
  }

  func start() {
    window.rootViewController = rootViewController
    let vc = HomeViewController.instantiate()
    vc.coordinator = self
    rootViewController.pushViewController(vc, animated: false)
    window.makeKeyAndVisible()
  }

  func showDetailView(item: Item) {
    let vc = DetailViewController.instantiate()
    vc.item = item
    vc.coordinator = self
    rootViewController.pushViewController(vc, animated: true)
  }
}

✌️ Adapter Pattern

Objetivo: 2 interfaces no relacionadas puedan trabajar juntas sin ningun tipo de problema.

Screenshot 2022-11-26 at 19 26 34

protocol OperationTarget {
    var getSum: String {get}
}

class OperationAdaptee {
    var a: Int
    var b: Int
    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }
    
    func sum() -> Int {
        return a + b
    }
}

class OperationAdapter: OperationTarget {
    let adaptee: OperationAdaptee
    
    init(adaptee: OperationAdaptee) {
        self.adaptee  = adaptee
    }
    var getSum: String {
        return String(self.adaptee.sum())
    }
}

// Test
func testAdapter() {
    let adaptee = OperationAdaptee(a: 3, b: 4)
    if (adaptee.sum() == 7) {
        print("Ok int")
    }
    
    let target = OperationAdapter(adaptee: adaptee)
    if target.getSum == "7" {
        print("Ok String")
    }
    print(target.getSum)
}

testAdapter()

Advantage of Adapter Pattern

  • 🟢 Single Responsibility Principle. You can separate the interface or data conversion code from the primary business logic of the program.
  • 🟢 Open/Closed Principle. You can introduce new types of adapters into the program without breaking the existing client code, as long as they work with the adapters through the client interface.
  • Interoperability between classes: Facilitates collaboration between objects with incompatible interfaces.
  • Code reuse: Allows the use of existing classes without having to modify their code.
  • Separation of responsibilities: Helps follow the single responsibility principle, keeping adaptation logic separate from the business code.
  • Design flexibility: Increases flexibility by allowing changes to interfaces without altering the code that depends on them.

Disadvantages of Adapter Patter

  • 🔴 The overall complexity of the code increases because you need to introduce a set of new interfaces and classes. Sometimes it’s simpler just to change the service class so that it matches the rest of your code.
  • Complejidad adicional: Introduce una capa extra de complejidad en el diseño de la aplicación.
  • Impacto en el rendimiento: Puede causar una sobrecarga leve debido a la abstracción adicional.
  • Mantenimiento: Si se usa en exceso o de forma inapropiada, puede complicar el mantenimiento del código.
  • Rigidez: En casos de adaptaciones complejas o cambios frecuentes en la interfaz objetivo, puede resultar en rigidez y necesidad de actualización constante del adaptador.

🔙 Back To Menu

Decorator

Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.

El patrón decorator es un patrón de diseño de software que permite añadir comportamientos a un objeto existente en tiempo de ejecución, de manera dinámica. Esto se logra envolviendo el objeto existente en uno o más objetos decorator que proporcionan el comportamiento adicional.

Aquí hay un ejemplo simple de cómo implementar el patrón decorator en Swift:

// La clase base que queremos decorar
class Beverage {
  var description: String = "Unknown Beverage"

  func getDescription() -> String {
    return description
  }

  func cost() -> Double {
    fatalError("cost() has not been implemented")
  }
}

// Una clase decoradora concreta
class Mocha: Beverage {
  override init() {
    super.init()
    description = "Mocha"
  }

  override func cost() -> Double {
    return 0.20 + super.cost()
  }
}

// Otra clase decoradora concreta
class Whip: Beverage {
  override init() {
    super.init()
    description = "Whip"
  }

  override func cost() -> Double {
    return 0.10 + super.cost()
  }
}

// Creamos una bebida simple
let simpleBeverage = Beverage()
simpleBeverage.getDescription()  // "Unknown Beverage"

// Creamos una bebida con una capa de Mocha
let mochaBeverage = Mocha(simpleBeverage)
mochaBeverage.getDescription()  // "Mocha, Unknown Beverage"

// Creamos una bebida con dos capas: Mocha y Whip
let doubleBeverage = Whip(Mocha(simpleBeverage))
doubleBeverage.getDescription()  // "Whip, Mocha, Unknown Beverage"

🪟 Facade

Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.

Problem

Imagine that you must make your code work with a broad set of objects that belong to a sophisticated library or framework. Ordinarily, you’d need to initialize all of those objects, keep track of dependencies, execute methods in the correct order, and so on.

As a result, the business logic of your classes would become tightly coupled to the implementation details of 3rd-party classes, making it hard to comprehend and maintain

Solution

A facade is a class that provides a simple interface to a complex subsystem which contains lots of moving parts. A facade might provide limited functionality in comparison to working with the subsystem directly. However, it includes only those features that clients really care about.

Having a facade is handy when you need to integrate your app with a sophisticated library that has dozens of features, but you just need a tiny bit of its functionality.

For instance, an app that uploads short funny videos with cats to social media could potentially use a professional video conversion library. However, all that it really needs is a class with the single method encode(filename, format). After creating such a class and connecting it with the video conversion library, you’ll have your first facade.

Screenshot 2022-11-28 at 05 52 33

protocol CreditModule {
    func showCredit()
}

class BlackModule: CreditModule {
    func showCredit() {
        print("La tarjeta Black tiene un credito de un 1.000.000")
    }
}

class GoldModule: CreditModule {
    func showCredit() {
        print("La tarjeta Gold tiene un credito de 50.000")
    }
}

class SilverModule: CreditModule {
    func showCredit() {
        print("La tarjeta Silver tiene un credito de 50.000")
    }
}

class CreditMarketFacade {
    var black = BlackModule()
    var gold = GoldModule()
    var silver = SilverModule()
    
    func showCreditBlack() {
        black.showCredit()
    }
    
    func showCreditGold() {
        gold.showCredit()
    }
    
    func showCreditSilver() {
        silver.showCredit()
    }
    
}

/// Test
func testFacade() {
    let facade = CreditMarketFacade()
    facade.showCreditBlack()
    facade.showCreditGold()
    facade.showCreditSilver()
}

testFacade()

🔙 Back To Menu

👮‍♀️ Proxy pattern

Un proxy controla el acceso al objeto original, lo que le permite realizar algo antes o después de que la solicitud llegue al objeto original.

¿cuál es el beneficio? Si necesita ejecutar algo antes o después de la lógica principal de la clase, el proxy le permite hacerlo sin cambiar esa clase. Dado que el proxy implementa la misma interfaz que la clase original, se puede pasar a cualquier cliente que espere un objeto de servicio real.

Por lo tanto, las llamadas al objeto acaban ocurriendo indirectamente a traves del objeto proxy es el que actua como sustitu del objeto original, delegando las llamadas a los metodos de los objetos

La clase Proxy tiene un campo de referencia que apunta a un objeto de servicio(clase a controlar). Una vez que el proxy finaliza su procesamiento (p. ej., inicialización diferida, registro, control de acceso, almacenamiento en caché, etc.), pasa la solicitud al objeto de servicio.

Por lo general, los proxies administran el ciclo de vida completo de sus objetos de servicio.

💡 Formas de utilizar el patrón Proxy

🐞 Inicialización diferida (proxy virtual). Esto es cuando tiene un objeto de servicio pesado que desperdicia recursos del sistema al estar siempre activo, aunque solo lo necesite de vez en cuando.

⚡️ En lugar de crear el objeto cuando se inicia la aplicación, puede retrasar la inicialización del objeto hasta el momento en que realmente se necesite.

🐞 Control de acceso (proxy de protección). Aquí es cuando desea que solo los clientes específicos puedan usar el objeto de servicio; por ejemplo, cuando sus objetos son partes cruciales de un sistema operativo y los clientes son varias aplicaciones lanzadas (incluidas las maliciosas).

⚡️ El proxy puede pasar la solicitud al objeto de servicio solo si las credenciales del cliente coinciden con algunos criterios.

🐞 Ejecución local de un servicio remoto (proxy remoto). Esto es cuando el objeto de servicio se encuentra en un servidor remoto.

⚡️ En este caso, el proxy pasa la solicitud del cliente a través de la red, manejando todos los detalles desagradables del trabajo con la red.

🐞 Solicitudes de registro (proxy de registro). Aquí es cuando desea mantener un historial de solicitudes al objeto de servicio.

⚡️ El proxy puede registrar cada solicitud antes de pasarla al servicio.n/

🐞 Almacenamiento en caché de los resultados de la solicitud (caching proxy). Aquí es cuando necesita almacenar en caché los resultados de las solicitudes de los clientes y administrar el ciclo de vida de este caché, especialmente si los resultados son bastante grandes.

⚡️ El proxy puede implementar el almacenamiento en caché para solicitudes recurrentes que siempre arrojan los mismos resultados. El proxy puede usar los parámetros de las solicitudes como claves de caché.

🐞 Referencia inteligente. Esto es cuando necesita poder descartar un objeto pesado una vez que no haya clientes que lo usen.

⚡️ El proxy puede realizar un seguimiento de los clientes que obtuvieron una referencia al objeto de servicio o sus resultados. De vez en cuando, el ⚡️ proxy puede revisar los clientes y verificar si todavía están activos. Si la lista de clientes se vacía, el proxy podría descartar el objeto de servicio y liberar los recursos del sistema subyacente.

El proxy también puede rastrear si el cliente modificó el objeto de servicio. Luego, los objetos sin modificar pueden ser reutilizados por otros clientes.

Screenshot 2022-12-01 at 07 41 08

protocol Internet {
    func connectTo(url: String)
}

class AccessToInternet: Internet {
    func connectTo(url: String) {
        print("Conectando a \(url)")
    }
}

class ProxyInternet: Internet {
    var internet = AccessToInternet()
    var bannedUrl = [String]()
    
    init() {
        bannedUrl.append("twitter.com")
        bannedUrl.append("google.com")
        bannedUrl.append("facebook.com")
    }
    
    func connectTo(url: String) {
        if (bannedUrl.contains(url)) {
            print("Intentando conectar \(url)")
            print("URL bloqueada - Accesso Denegado - Consulta a tu Administrador")
        } else {
            internet.connectTo(url: url)
        }
    }
}

// TEST

func testProxy() {
    let internet = ProxyInternet()
    internet.connectTo(url: "udemy.com")
    internet.connectTo(url: "twitter.com")
}


testProxy()

Advantage of Proxy Pattern

  • 🟢 Puede controlar el objeto de servicio sin que los clientes lo sepan.
  • 🟢 Puede administrar el ciclo de vida del objeto de servicio cuando a los clientes no les importa.
  • 🟢 El proxy funciona incluso si el objeto de servicio no está listo o disponible.
  • 🟢 Principio abierto/cerrado . Puede introducir nuevos proxies sin cambiar el servicio o los clientes.

Disadvantages of Proxy Patter

  • 🔴 El código puede volverse más complicado ya que necesita introducir muchas clases nuevas.
  • 🔴 La respuesta del servicio puede retrasarse.

🔙 Back To Menu

Releases

No releases published

Packages

 
 
 

Languages