SOLID

Single Responsibility

Establece que cada módulo debe tener una responsabilidad única, lo cual significa que tiene que ser altamente cohesivo e implementar una logica fuertemente relacionada.

Ejemplo

Malo

class PlaceOrder {
  let product: Product
  init(product: Product) {
    self.product = product
  }

  func run() {
    // 1. Lógica relacionada a verification
    //    de disponibilidad en inventario
    // 2. Lógica relacionada a procesamiento de pago
    // 3. Lógica relacionada a procesamiento de envio
  }
}

Bueno

class PlaceOrder {
  let product: Product
  init(product: Product) {
    self.product = product
  }

  func run() {
    StockAvailability(product: self.product).run()
    ProductPayment(product: self.product).run()
    ProductShipment(product: self.product).run()
  }
}

Open Closed Principle

Establece que los modulos de software deben estar abiertos para extensión, pero cerrados para modificación; es decir, que un modulo de este tipo puede permitir que su comportamiento se amplíe sin modificar su implementación.

Ejemplo

Malo

class Logger {
  init(loggingForm: String) {
    self.loggingForm = loggingForm
  }

  func log(message: String) {
    switch self.loggingForm {
      case "console":
        print(message)
      case "file":
        DiskStore().write("logs.txt", message: message)
    }
  }
}

Bueno

protocol Logger {
  func log(_ message: String)
}

class EventTracker {
  let logger: Logger
  init(logger: Logger) {
    self.logger = logger
  }

  func log(message: String) {
    self.logger.log(message)
  }
}

class ConsoleLogger: Logger {
  func log(_ message: String) {
    print(message)
  }
}

class FileLogger: Logger {
  func log(_ message: String) {
      DiskStore().write("logs.txt", message: message)
  }
}

Liskov Subtitution Principle

Se define como una extensión del ‘Open Closed Principle’ que establece que las nuevas clases derivadas que extienden la clase base no deberían cambiar el comportamiento de la clase base (comportamiento de los métodos heredados). Siempre que una clase Y sea una subclase de la clase X, cualquier instancia que haga referencia a la clase X también debería poder hacer referencia a la clase Y (los tipos derivados deben ser completamente sustituibles por sus tipos base).

Ejemplo

Malo

class Rectangle {
  let width: Int
  let height: Int
  init(width: Int, height: Int) {
    self.width = width
    self.height = height
  }

  mutating func setWidth(width: Int){
    self.width = width
  }

  mutating func setHeight(height: Int) {
    self.height = height
  }
}

class Square: Rectangle {
  // Violación LSP: clase heredada sobrescribe comportamiento del padre

  override func setWidth(width: Int){
    super.setWidth(width: width)
    self.width = width
  }

  override func setHeight(height: Int) {
    super.setHeight(height: height)
    self.height = height
  }
}

Interface Segregation Principle

Establece que ningún cliente debe ser obligado a depender de los métodos que no usa. El ISP divide las interfaces que son muy grandes en otras más pequeñas y más específicas para que los clientes solo tengan que conocer los métodos que les interesan. Estas interfaces reducidas también se denominan interfaces de rol. Este principio está destinado a mantener un sistema desacoplado y, por lo tanto, más fácil de refactorizar, cambiar y redistribuir.

Ejemplo

Malo

protocol Car {
  func open()
  func startEngine()
  func changeEngine()
}

// ISP violation: La instancia Driver
// no hace uso de #changeEngine
class Drive {
  func takeARide(car: Car) {
    car.open()
    car.startEngine()
  }
}

// ISP violation: La instancia Mechanic
// no hace uso de #startEngine
class Mechanic {
  func repair(car: Car) {
    car.open()
    car.changeEngine()
  }
}

Bueno

protocol Openable {
  func open()
}

protocol Driveable: Openable {
  func startEngine()
}

protocol Fixable: Openable {
  func changeEngine()
}

protocol Car: Dirveable, Fixable {}

class Drive {
  func takeARide(vehicle: DriveAble) {
     vehicle.startEngine()
  }
}

class Mechanic {
  func repair(vehicle: Fixable) {
    vehicle.changeEngine()
  }
}

Dependency Inversion Principle

Se refiere a una forma específica de desacoplar los módulos de software. Al seguir este principio, las relaciones de dependencia convencionales establecidas desde módulos de alto nivel de configuración de políticas a módulos de dependencia de bajo nivel se invierten, lo que hace que los módulos de alto nivel sean independientes de los detalles de implementación del módulo de bajo nivel. El principio establece:

  • Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de las abstracciones.
  • Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.

Ejemplo

Malo

class EventTracker {
  let logger: ConsoleLogger
  init() {
    // Una instancia de bajo nivel ConsoleLogger
    // es directemente creade dentro de una de alto nivel.
    // La clase EventTracker aumenta el acoplamiento
    self.logger = ConsoleLogger.new
  }

  func log(message: String) {
    self.logger.log(message)
  }
}

Bueno

protocol Logger {
  func log(_ message: String)
}

class EventTracker {
  let logger: Logger
  init(logger: Logger = ConsoleLogger()) {
    self.logger = logger
  }

  // Por medio de injección de dependencias en el
  // Open Closed Principle
  func log(message: String) {
    self.logger.log(message)
  }
}