SOLID Principles

Concepts

Single-responsibility principle (SRP)

A class should only have a single responsibility, that is, only changes to one part of the software’s specification should be able to affect the specification of the class.

Open–closed principle (OCP)

“Software entities … should be open for extension, but closed for modification.”

Liskov substitution principle (LSP)

“Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.” Child classes should never break parent classes

Interface segregation principle (ISP)

“Many client-specific interfaces are better than one general-purpose interface.” Make fine grained interface that are client specific

Dependency inversion principle (DIP)

One should “depend upon abstractions, [not] concretions.” higher level modules shouldn’t depend on the lower level modules



What they solve?

  1. Delicacy - Change in one place breaks unexpected parts.
  2. Immobility - reusable code
  3. Irritable - takes a lot of effort for change because it efferts sevaral parts.

Details

S - Single Responsibility Principle — SRP

This principle states that a class should do only one job! Yes this is the meaning of single responsibility and there is no need to sugar coat it. “A class = performs one operation” simple and stupid.

protocol SwitchOn {
    func on()
}

protocol SwitchOff {
    func off()
}

class Switch: SwitchOn, SwitchOff {
    private var state: Bool = false

    func on() {
        state = true
    }

    func off() {
        state = false
    }

    var description: String {
        return state ? "Switched On" : "Switched Off"
    }
}

let aSwitch = Switch() /// Represents your model
aSwitch.off()
aSwitch.on()

While the above code is totally correct but it is “anti-pattern”. For us to be cohesive we need to perform these actions based on the given contract, which is through the Interface and not directly accessing the Concrete Type.

Refactoring the code

/// Executes a function
protocol Executable {
    func execute()
}
/// Conforms to `Executable`
class TurnOn: Executable {

    private let  aSwitch: SwitchOn

    init(aSwitch: SwitchOn) {
        self.aSwitch = aSwitch
    }

    func execute() {
        //Single Responsibility
        self.aSwitch.on()
    }
}
/// Conforms to `Executable`
class TurnOff: Executable {
    private let  aSwitch: SwitchOff

    init(aSwitch: SwitchOff) {
        self.aSwitch = aSwitch
    }

    func execute() {
        //Single Responsibility
        self.aSwitch.off()
    }
}




O - Open/Closed Principle

In object-oriented programming, the open–closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behaviour to be extended without modifying its source code.

The Open Closed principle enforces a rule that a class can be easily extended but at the same time forbids any modification to it’s self. This principle highly draws it’s traits from the Decorator & Strategy pattern. The Decorator allows extension of behaviour dynamically or statically during runtime. It is often referred to as “the alternative to subclassing”, while the Strategy allows implementation of different algorithms interchangeable within the same family.

//MARK: Extension for interchangeable algorithms
extension Developer where Self: IOSEngineer {
    func traits() -> String { return iosTraits }
}

extension Developer where Self == WebProgrammer {
    func traits() -> String {
        return "We are cool and we do both \(self.language) and CSS also."
    }
}




L - Liskov substitution principle

Child classes should never break parent classes

Liskov substitution principle states that all derived class should be substitutable for it’s original base class. What this means in practice is that a subclass should always be interchangeable for it’s super class. The main purpose of this principle is to guarantee semantic interoperability within the types hierarchy.

class Vehicle {
    let name: String
    let speed: Double
    let wheels: Int

    init(name: String, speed: Double, wheels: Int) {
        self.name = name
        self.speed = speed
        self.wheels = wheels
    }

    func canfly() -> Bool {
        return false
    }
}

/// Airplane is a subclass of Vehicle
class Airplane: Vehicle {
    override func canfly() -> Bool {
        return true
    }
}

/// Car is derived from Vehicle. 
class Car: Vehicle { }

class Handler {
    static func speedDescription(for vehicle: Vehicle) -> String {
        ///Vehicle could be an Airplane or Car 
        ///This is substitutability or interchaneable
        if vehicle.canfly() {
            return "It flys at \(vehicle.speed) knot "
        } else {
            return "Runs at \(vehicle.speed) kph"
        }
    }
}

Liskov Substitution guarantees that our speedDescription algorithm will keep functioning regardless of which Subclass Type it receives as an argument. This term is known as semantic interoperability.

let car = Car(name: "BMW", speed: 180.00, wheels: 4)
let plane = Airplane(name: "KLM", speed: 34.00, wheels: 3)

///Based on Liskov Handler should always be able to
///Process speedDescription when given any type of Vehicle

let carSpeedDescription = Handler.speedDescription(for: car)
let planeSpeedDescription = Handler.speedDescription(for: plane)

In other terms, The Liskov Substitution Principle (LSP) states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. What that means is that when you inherit from a class or an abstract class or implement an interface (protocol), your objects should be replaceable and injectable wherever that interface or class that you subclassed from was used. This principle is often referred to as design by contract or, as of late in the Swift community, referred to as protocol-oriented programming. The main message of this principle is that you should not violate the contract that your interfaces you subclass from promise to fulfill, and that by subclassing, those subclasses could be used anywhere that the superclass was previously used.




I — Interface Segregation

Make fine grained interface that are client specific

The Interface Segregation Principle deals separation of concerns. It states that a client should not be forced to implement or depend upon methods that it does not use. It is better to have many specific interfaces than to have a monolithic general purpose interface. The entire purpose of Interface Segregation is to reduce interface bloat or what is known as interface pollution and to favour code readability.

Example
  1. UITableViewDataSource, UITableViewDelegate protocols
  2. UIKit, ARKit




D - Dependency Inversion Principle

Dependency Inversion Principle states that, higher level modules shouldn’t depend on the lower level modules, they should both depend on abstractions. In other words all entities should be based on Abstract Interfaces and not on Concrete Types. The principle also stressed that abstractions should not depend on details. Details should depend upon abstractions.

Example
  1. Remote file server client
  2. Model View Controller




References
  1. medium - part 1
  2. medium - part 2
  3. medium - part 3
  4. medium - part 4
  5. medium - part 5
  6. Wiki
  7. iosinterviewguide